XUnit DI через переопределенный файл запуска (.net core)

Я построил WebAPI и помимо моих тестов, работающих на Postman, я хотел бы реализовать некоторые интеграционные / модульные тесты.

Теперь моя бизнес-логика очень тонкая, в большинстве случаев ее больше из грубых действий, поэтому я хотел начать с тестирования моих контроллеров.

У меня есть базовая настройка. Шаблон репозитория (интерфейсы), службы (бизнес-логика) и контроллеры.
Поток идет контроллер (DI Service) — > Service (DI Repo) — > > Repo действие!

Таким образом, то, что я сделал, было переопределить мой файл запуска, чтобы изменить в базу данных в памяти, а остальное должно быть хорошо (я бы предположил) услуги добавляются, repos добавляются, и теперь я указываю в БД в памяти, которая хороша для моего основного тестирования.

namespace API.UnitTests
{    
    public class TestStartup : Startup
    {
        public TestStartup(IHostingEnvironment env)
            : base(env)
        {

        }

        public void ConfigureTestServices(IServiceCollection services)
        {
            base.ConfigureServices(services);
            //services.Replace<IService, IMockedService>();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            base.Configure(app, env, loggerFactory);
        }

        public override void SetUpDataBase(IServiceCollection services)
        {
            var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
            var connectionString = connectionStringBuilder.ToString();
            var connection = new SqliteConnection(connectionString);

            services
                .AddEntityFrameworkSqlite()
                .AddDbContext<ApplicationDbContext>(
                    options => options.UseSqlite(connection)
                );
        }
    }
}

Я написал свой первый тест, но DatasourceService там нет:

Следующие параметры конструктора не имели совпадающих данных приспособления: DatasourceService datasourceService

namespace API.UnitTests
{
    public class DatasourceControllerTest
    {
        private readonly DatasourceService _datasourceService; 

        public DatasourceControllerTest(DatasourceService datasourceService)
        { 
            _datasourceService = datasourceService;            
        }

        [Xunit.Theory,
        InlineData(1)]
        public void GetAll(int companyFk) {
            Assert.NotEmpty(_datasourceService.GetAll(companyFk));
        }
    }
}

Чего мне не хватает?

1 ответ

  1. Нельзя использовать инъекцию зависимостей для тестовых классов. Вы можете только позволить xunit вводить специальные приспособления через конструктор (см. docs ).

    Для интеграционного тестирования необходимо использовать TestServerкласс из Microsoft.AspNetCore.TestHostпакета и отдельный Startup.csкласс (проще настроить конфигурацию, чем наследование imho).

    public class TestStartup : Startup
    {
        public TestStartup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }
    
        public IConfigurationRoot Configuration { get; }
    
        public void ConfigureTestServices(IServiceCollection services)
        {
            services.Replace(ServiceDescriptor.Scoped<IService, MockedService>());
            services.AddEntityFrameworkSqlite()
                .AddDbContext<ApplicationDbContext>(
                    options => options.UseSqlite(connection)
                );
        }
    
        public void Configure(IApplicationBuilder app)
        {
            // your usual registrations there
        }
    }
    

    В проекте модульного тестирования необходимо создать экземпляр TestServerи выполнить тест.

    public class DatasourceControllerTest
    {
        private readonly TestServer _server; 
        private readonly HttpClient _client;
    
        public DatasourceControllerTest()
        {
            // Arrange
            _server = new TestServer(new WebHostBuilder()
                .UseStartup<TestStartup>());
            _client = _server.CreateClient();
        }
    
        [Xunit.Theory,
        InlineData(1)]
        public async Task GetAll(int companyFk) {
            // Act
            var response = await _client.GetAsync($"/api/datasource/{companyFk}");
            // expected result from rest service
            var expected = @"[{""data"":""value1"", ""data2"":""value2""}]";
    
            // Assert
            // This makes sure, you return a success http code back in case of 4xx status codes 
            // or exceptions (5xx codes) it throws an exception
            response.EnsureSuccessStatusCode();
    
            var resultString = await response.Content.ReadAsStringAsync();
            Assert.Equals(resultString, expectedString);
        }
    }
    

    Теперь, когда вы вызываете операции, которые пишут в базу данных, вы также можете проверить, действительно ли данные записаны в базу данных:

    [Xunit.Theory,
    InlineData(1)]
    public async Task GetAll(int companyFk) {
        // Act
        var response = await _client.DeleteAsync($"/api/datasource/{companyFk}");
        // expected result from rest service
    
        // Assert
        response.EnsureSuccessStatusCode();
    
        // now check if its really gone in the database. For this you need an instance 
        // of the in memory Sqlite DB. TestServer has a property Host, which is an IWebHost
        // and it has a property Services which is the IoC container
    
        var provider = _server.Host.Services;
        var dbContext = provider.GetRequiredService<ApplicationDbContext>();
    
        var result = await dbContext.YourTable.Where(entity => entity.Id == companyFk).Any();
    
        // if it was deleted, the query should result in false
        Assert.False(result);
    }