web-dev-qa-db-fra.com

Test et simulation d'intégration ASP.NET Core à l'aide de Moq

J'ai ce qui suit test d'intégration ASP.NET Core en utilisant un WebApplicationFactory personnalisé

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;
    }
}

qui semble fonctionner mais le problème est que la méthode ConfigureTestServices n'est jamais appelée, donc ma maquette n'est jamais enregistrée avec le conteneur IoC. Vous pouvez trouver le code source complet ici .

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();
}
6

Vous devez créer un faux démarrage:

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>();
    }
}

Ensuite, utilisez-le avec IClassFixture<CustomWebApplicationFactory<FakeStartup>>.

Assurez-vous de créer votre méthode virtuelle ConfigureServices d'origine.

2
Tuyen Pham

La meilleure façon de gérer cela est de factoriser les parties de votre Startup qui devront être remplacées pendant le test. Par exemple, au lieu d'appeler services.AddDbContext<MyContext>(...); directement dans ConfigureServices, créez une méthode privée virtuelle comme:

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

Ensuite, dans votre projet de test, créez une classe comme TestStartup qui dérive de la classe Startup de votre SUT. Ensuite, vous pouvez remplacer ces méthodes virtuelles pour les sous-traiter dans vos services de test, vos simulations, etc.

Enfin, faites quelque chose comme:

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