web-dev-qa-db-ja.com

ASP.NET Core統合テストとMoqを使用したモッキング

次の ASP.NET Core統合テスト カスタムWebApplicationFactoryを使用しています

public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint>
    where TEntryPoint : class
{
    public CustomWebApplicationFactory()
    {
        this.ClientOptions.AllowAutoRedirect = false;
        this.ClientOptions.BaseAddress = new Uri("https://localhost");
    }

    public ApplicationOptions ApplicationOptions { get; private set; }

    public Mock<IClockService> ClockServiceMock { get; private set; }

    public void VerifyAllMocks() => Mock.VerifyAll(this.ClockServiceMock);

    protected override TestServer CreateServer(IWebHostBuilder builder)
    {
        this.ClockServiceMock = new Mock<IClockService>(MockBehavior.Strict);

        builder
            .UseEnvironment("Testing")
            .ConfigureTestServices(
                services =>
                {
                    services.AddSingleton(this.ClockServiceMock.Object);
                });

        var testServer = base.CreateServer(builder);

        using (var serviceScope = testServer.Host.Services.CreateScope())
        {
            var serviceProvider = serviceScope.ServiceProvider;
            this.ApplicationOptions = serviceProvider.GetRequiredService<IOptions<ApplicationOptions>>().Value;
        }

        return testServer;
    }
}

うまくいくように見えますが、問題はConfigureTestServicesメソッドが呼び出されないため、モックがIoCコンテナに登録されないことです。完全なソースコード here を見つけることができます。

public class FooControllerTest : IClassFixture<CustomWebApplicationFactory<Startup>>, IDisposable
{
    private readonly HttpClient client;
    private readonly CustomWebApplicationFactory<Startup> factory;
    private readonly Mock<IClockService> clockServiceMock;

    public FooControllerTest(CustomWebApplicationFactory<Startup> factory)
    {
        this.factory = factory;
        this.client = factory.CreateClient();
        this.clockServiceMock = this.factory.ClockServiceMock;
    }

    [Fact]
    public async Task Delete_FooFound_Returns204NoContent()
    {
        this.clockServiceMock.SetupGet(x => x.UtcNow).ReturnsAsync(new DateTimeOffset.UtcNow);

        var response = await this.client.DeleteAsync("/foo/1");

        Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
    }

    public void Dispose() => this.factory.VerifyAllMocks();
}

偽のスタートアップを作成する必要があります。

public class FakeStartup : Startup
{
    public FakeStartup(IConfiguration configuration)
        : base(configuration)
    {
    }

    public override void ConfigureServices(IServiceCollection services)
    {
        base.ConfigureServices(services);

        // Your fake go here
        //services.AddScoped<IService, FakeService>();
    }
}

次に、IClassFixture<CustomWebApplicationFactory<FakeStartup>>で使用します。

オリジナルのConfigureServices仮想メソッドを作成してください。

2
Tuyen Pham

これを処理する最良の方法は、テスト中に置き換える必要があるStartupの一部を除外することです。たとえば、ConfigureServicesservices.AddDbContext<MyContext>(...);を直接呼び出す代わりに、次のような仮想プライベートメソッドを作成します。

private virtual void ConfigureDatabase(IServiceCollection services)
{
    services.AddDbContext<MyContext>(...);
}

次に、テストプロジェクトで、SUTのTestStartupクラスから派生するStartupのようなクラスを作成します。次に、これらの仮想メソッドをオーバーライドして、テストサービスやモックなどをサブインします。

最後に、次のようにします。

builder
    .UseEnvironment("Testing")
    .UseStartup<TestStartup>();
2
Chris Pratt