機能テストの実装.XUnitによるネット


この記事では、機能テストを実装する方法を学びます.XUnitを使ったネットAPI.
必要条件:
  • Visual Studio 2022を使用します.ネット6 SDK
  • プロジェクトのダウンロードhere
  • テストされたプロジェクトは、しばしばシステムの下でテストされます.
    機能テストは、通常の手配、アクト、およびテストステップを含む一連のイベントに従います.
  • SUSTのWebホストが設定されています.
  • テストサーバークライアントは、アプリケーションに要求を送信するために作成されます.
  • アレンジテストステップが実行されます:テストアプリケーションは、要求を準備します.
  • ACTテストステップが実行されます.クライアントは要求を送信し、応答を受け取ります.
  • assertテストステップは実行されます:実際の応答は予想された応答に基づいてパスまたは失敗として検証されます.
  • テストの全てが実行されるまで、プロセスは続く.
  • 試験結果を報告した.
  • 開発を始めましょう.
    次の構造を持つ必要があります.

    テストフォルダーで、右クリックします.新しいプロジェクトを追加ストアという名前のXUnitテストプロジェクト.テストと選択します.ターゲットフレームワークとしてのNET 6.

    リファレンスを追加するStore.SharedDatabaseSetup and Store.WebApi プロジェクト.
    次のパッケージをインストールします
  • マイクロソフト.アスピネット.MVCテスト
  • マイクロソフト.EntityFrameworkCore.記憶
  • ASP .NETコア6はWebアプリケーションを導入しましたStartup クラス.試験するWebApplicationFactory なしでStartup クラス、ASP .NETコア6アプリは暗黙の定義を公開する必要がありますProgram クラス.だから、店で.WebAPIプロジェクトを作成する必要がありますProgram 部分クラス宣言を使用したクラス公開
    ...
    public partial class Program { }
    
    Webアプリケーションの変更を行った後、テストプロジェクトではProgram クラス用WebApplicationFactory .WebApplicationFactory<TEntryPoint> は、関数テストのテストサーバを作成するために使用されます.TEntryPoint SUTのエントリポイントクラスは、通常起動クラスです.
    インストア.「機能をテスト」プロジェクトは、CustomWebApplicationFactoryクラスを作成します.
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Mvc.Testing;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    using Store.Infrastructure.Persistence.Contexts;
    using Store.SharedDatabaseSetup;
    using System;
    using System.Linq;
    
    namespace Store.FunctionalTests
    {
        public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
        {
    
            protected override void ConfigureWebHost(IWebHostBuilder builder)
            {
                builder.ConfigureServices(services =>
                {
                    // Remove the app's StoreContext registration.
                    var descriptor = services.SingleOrDefault(
                        d => d.ServiceType ==
                            typeof(DbContextOptions<StoreContext>));
    
                    if (descriptor != null)
                    {
                        services.Remove(descriptor);
                    }
    
                    // Add StoreContext using an in-memory database for testing.
                    services.AddDbContext<StoreContext>(options =>
                    {
                        options.UseInMemoryDatabase("InMemoryDbForFunctionalTesting");
                    });
    
                    // Get service provider.
                    var serviceProvider = services.BuildServiceProvider();
    
                    using (var scope = serviceProvider.CreateScope())
                    {
                        var scopedServices = scope.ServiceProvider;
    
                        var logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
    
                        var storeDbContext = scopedServices.GetRequiredService<StoreContext>();
                        storeDbContext.Database.EnsureCreated();
    
                        try
                        {
                            DatabaseSetup.SeedData(storeDbContext);
                        }
                        catch (Exception ex)
                        {
                            logger.LogError(ex, $"An error occurred seeding the Store database with test messages. Error: {ex.Message}");
                        }
                    }
                });
            }
    
            public void CustomConfigureServices(IWebHostBuilder builder)
            {
                builder.ConfigureServices(services =>
                {
                    // Get service provider.
                    var serviceProvider = services.BuildServiceProvider();
    
                    using (var scope = serviceProvider.CreateScope())
                    {
                        var scopedServices = scope.ServiceProvider;
    
                        var logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
    
                        var storeDbContext = scopedServices.GetRequiredService<StoreContext>();
    
                        try
                        {
                            DatabaseSetup.SeedData(storeDbContext);
                        }
                        catch (Exception ex)
                        {
                            logger.LogError(ex, $"An error occurred seeding the Store database with test messages. Error: {ex.Message}");
                        }
                    }
                });
            }
        }
    }
    
    CustomWebApplicationFactory から継承するWebApplicationFactory オーバーライドConfigureWebHost . The IWebHostBuilder サービスコレクションの構成を許可しますConfigureServices .
    sutのデータベースコンテキストはStartup.ConfigureServices メソッド.テストアプリケーションのbuilder.ConfigureServices コールバックは、アプリケーションの後に実行されますStartup.ConfigureServices コードが実行されます.
    サンプルアプリケーションは、データベースコンテキストのサービスディスクリプタを見つけ、ディスクリプタを使用してサービス登録を削除します.次に、工場は新しいStoreContext これは、テスト用のメモリデータベースを使用します.
    テストクラスはクラスフィクスチャインターフェイスを実装する(IClassFixture) クラスをテストするには、クラスのテストで共有オブジェクトインスタンスを提供します.
    コントローラフォルダを作成します.
    using System.Net.Http;
    using Xunit;
    
    namespace Store.FunctionalTests.Controllers
    {
        public class BaseControllerTests : IClassFixture<CustomWebApplicationFactory<Program>>
        {
            private readonly CustomWebApplicationFactory<Program> _factory;
    
            public BaseControllerTests(CustomWebApplicationFactory<Program> factory)
            {
                _factory = factory;
            }
    
            public HttpClient GetNewClient()
            {
                var newClient = _factory.WithWebHostBuilder(builder =>
                {
                    _factory.CustomConfigureServices(builder);
                }).CreateClient();
    
                return newClient;
            }
        }
    }
    
    CustomConfigureServices メソッドCustomWebApplicationFactory クラスをクライアントをカスタマイズするために作成されましたWithWebHostBuilder . 別のテストではデータベースの変更を行い、他のテストの前に実行することができます.
    ProductsController Testクラスを作成し、BaseControllerTestから拡張します.
    using Newtonsoft.Json;
    using Store.ApplicationCore.DTOs;
    using Store.FunctionalTests.Models;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Text;
    using System.Threading.Tasks;
    using Xunit;
    
    namespace Store.FunctionalTests.Controllers
    {
        public class ProductsControllerTests : BaseControllerTests
        {
            public ProductsControllerTests(CustomWebApplicationFactory<Program> factory) : base(factory)
            {
            }
    
            [Fact]
            public async Task GetProducts_ReturnsAllRecords()
            {
                var client = this.GetNewClient();
                var response = await client.GetAsync("/api/Products");
                response.EnsureSuccessStatusCode();
    
                var stringResponse = await response.Content.ReadAsStringAsync();
                var result = JsonConvert.DeserializeObject<IEnumerable<ProductResponse>>(stringResponse).ToList();
                var statusCode = response.StatusCode.ToString();
    
                Assert.Equal("OK", statusCode);
                Assert.True(result.Count == 10);
            }
    
            [Fact]
            public async Task GetProductById_ProductExists_ReturnsCorrectProduct()
            {
                var productId = 5;
                var client = this.GetNewClient();
                var response = await client.GetAsync($"/api/Products/{productId}");
                response.EnsureSuccessStatusCode();
    
                var stringResponse = await response.Content.ReadAsStringAsync();
                var result = JsonConvert.DeserializeObject<SingleProductResponse>(stringResponse);
                var statusCode = response.StatusCode.ToString();
    
                Assert.Equal("OK", statusCode);
                Assert.Equal(productId, result.Id);
                Assert.NotNull(result.Name);
                Assert.True(result.Price > 0);
                Assert.True(result.Stock > 0);
            }
    
            [Theory]
            [InlineData(0)]
            [InlineData(20)]
            public async Task GetProductById_ProductDoesntExist_ReturnsNotFound(int productId)
            {
                var client = this.GetNewClient();
                var response = await client.GetAsync($"/api/Products/{productId}");
    
                var statusCode = response.StatusCode.ToString();
    
                Assert.Equal("NotFound", statusCode);
            }
    
            [Fact]
            public async Task PostProduct_ReturnsCreatedProduct()
            {
                var client = this.GetNewClient();
    
                // Create product
    
                var request = new CreateProductRequest
                {
                    Description = "Description",
                    Name = "Test product",
                    Price = 25.3
                };
    
                var stringContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
    
                var response1 = await client.PostAsync("/api/Products", stringContent);
                response1.EnsureSuccessStatusCode();
    
                var stringResponse1 = await response1.Content.ReadAsStringAsync();
                var createdProduct = JsonConvert.DeserializeObject<SingleProductResponse>(stringResponse1);
                var statusCode1 = response1.StatusCode.ToString();
    
                Assert.Equal("Created", statusCode1);
    
                // Get created product
    
                var response2 = await client.GetAsync($"/api/Products/{createdProduct.Id}");
                response2.EnsureSuccessStatusCode();
    
                var stringResponse2 = await response2.Content.ReadAsStringAsync();
                var result2 = JsonConvert.DeserializeObject<SingleProductResponse>(stringResponse2);
                var statusCode2 = response2.StatusCode.ToString();
    
                Assert.Equal("OK", statusCode2);
                Assert.Equal(createdProduct.Id, result2.Id);
                Assert.Equal(createdProduct.Name, result2.Name);
                Assert.Equal(createdProduct.Description, result2.Description);
                Assert.Equal(createdProduct.Stock, result2.Stock);
            }
    
            [Fact]
            public async Task PostProduct_InvalidData_ReturnsErrors()
            {
                var client = this.GetNewClient();
    
                // Create product
    
                var request = new CreateProductRequest
                {
                    Description = "Description",
                    Name = null,
                    Price = 0
                };
    
                var stringContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
    
                var response = await client.PostAsync("/api/Products", stringContent);
    
                var stringResponse = await response.Content.ReadAsStringAsync();
                var badRequest = JsonConvert.DeserializeObject<BadRequestModel>(stringResponse);
                var statusCode = response.StatusCode.ToString();
    
                Assert.Equal("BadRequest", statusCode);
                Assert.NotNull(badRequest.Title);
                Assert.NotNull(badRequest.Errors);
                Assert.Equal(2, badRequest.Errors.Count);
                Assert.Contains(badRequest.Errors.Keys, k => k == "Name");
                Assert.Contains(badRequest.Errors.Keys, k => k == "Price");
            }
    
    
            [Fact]
            public async Task PutProduct_ReturnsUpdatedProduct()
            {
                var client = this.GetNewClient();
    
                // Update product
    
                var productId = 6;
                var request = new UpdateProductRequest
                {
                    Description = "Description",
                    Name = "Test product",
                    Price = 17.67,
                    Stock = 94
                };
    
                var stringContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
    
                var response1 = await client.PutAsync($"/api/Products/{productId}", stringContent);
                response1.EnsureSuccessStatusCode();
    
                var stringResponse1 = await response1.Content.ReadAsStringAsync();
                var updatedProduct = JsonConvert.DeserializeObject<SingleProductResponse>(stringResponse1);
                var statusCode1 = response1.StatusCode.ToString();
    
                Assert.Equal("OK", statusCode1);
    
                // Get updated product
    
                var response2 = await client.GetAsync($"/api/Products/{updatedProduct.Id}");
                response2.EnsureSuccessStatusCode();
    
                var stringResponse2 = await response2.Content.ReadAsStringAsync();
                var result2 = JsonConvert.DeserializeObject<SingleProductResponse>(stringResponse2);
                var statusCode2 = response2.StatusCode.ToString();
    
                Assert.Equal("OK", statusCode2);
                Assert.Equal(updatedProduct.Id, result2.Id);
                Assert.Equal(updatedProduct.Name, result2.Name);
                Assert.Equal(updatedProduct.Description, result2.Description);
                Assert.Equal(updatedProduct.Stock, result2.Stock);
            }
    
            [Fact]
            public async Task DeleteProductById_ReturnsNoContent()
            {
                var client = this.GetNewClient();
                var productId = 5;
    
                // Delete product
    
                var response1 = await client.DeleteAsync($"/api/Products/{productId}");
    
                var statusCode1 = response1.StatusCode.ToString();
    
                Assert.Equal("NoContent", statusCode1);
    
                // Get deleted product
    
                var response2 = await client.GetAsync($"/api/Products/{productId}");
    
                var statusCode2 = response2.StatusCode.ToString();
    
                Assert.Equal("NotFound", statusCode2);
            }
        }
    }
    
    ソリューションを右クリックし、「実行」をクリックします.
    テストエクスプローラーでわかるように、すべてのテストがパスしました.

    ソースコードを見つけることができますhere .
    読書ありがとう
    読んでくださってありがとうございます.この記事は面白くて、将来役に立つかもしれません.何か質問やアイデアを議論する必要がある場合は、一緒に知識を交換し、交換することができる喜びになります.