Radiofisik

my knowledge base

Тестирование контроллеров .net core

Поставим задачу создания интеграционных тестов для апи, возможно с подменой части зависимостей на заглушки. Для начала создадим проект .net core и посмотрим на код инициализации

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
                                  {
                                      webBuilder.UseStartup<Startup>();
                                  });
}

Будем использовать Autofac и Autofac.Extensions.DependencyInjection При этом традиционно конфигурация конейнера autofac прописывается в startup, но из-за бага .net core это не позволит переопределить зависимости при тестировании. Поэтому будем конфигурировать контейнер в Program.cs

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .UseServiceProviderFactory(new AutofacServiceProviderFactory())
    .ConfigureContainer<ContainerBuilder>(builder =>
                                          {
                                              builder.RegisterType<WeatherService>()
                                                  .AsImplementedInterfaces().InstancePerLifetimeScope();
                                          })
    .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });

Далее создадим тестовый проект xUnit, подключим зависимости Microsoft.AspNet.WebApi.Client и FluentAssertions Для упрощения переопределения зависимостей создадим класс

class TestWebApplicationFactory: WebApplicationFactory<Startup>
{
    private Action<ContainerBuilder> _setupMockServicesAction;

    public TestWebApplicationFactory WithMockServices(Action<ContainerBuilder> setupMockServices)
    {
        _setupMockServicesAction = setupMockServices;
        return this;
    }

    protected override IHost CreateHost(IHostBuilder builder)
    {
        builder.ConfigureContainer<ContainerBuilder>(containerBuilder =>
                                                     {
                                                         _setupMockServicesAction?.Invoke(containerBuilder);
                                                     });

        return base.CreateHost(builder);
    }
}

Тогда можно будет создать тест метода контроллера с переопределением зависимости

[Fact]
public async Task WeatherControllerTest()
{
    var mock = new Mock<IWeatherService>();
    mock.Setup(e => e.Get()).Returns(new List<WeatherForecast>());

    using var testWebApplicationFactory = new TestWebApplicationFactory()
        .WithMockServices(containerBuilder =>
                          {
                              containerBuilder.Register(s => mock.Object).AsImplementedInterfaces()
                                  .InstancePerLifetimeScope();
                          });

    var resp = await testWebApplicationFactory.CreateClient().GetAsync("/weatherforecast");

    resp.StatusCode.Should().Be(HttpStatusCode.OK);
    var result = await resp.Content.ReadAsStringAsync();
    result.Should().Be("[]");

    mock.Verify(m=>m.Get(), Times.AtLeastOnce);
}

Использованные материалы

https://github.com/autofac/Autofac/issues/1207

https://github.com/dotnet/aspnetcore/blob/a4d9faefe2ed6fbebd500413032336607ec92778/src/Hosting/TestHost/test/TestServerTests.cs#L51-L64

https://andrewlock.net/converting-integration-tests-to-net-core-3/