2016-12-27 5 views
2

У меня есть WebAPI, и помимо моих тестов, запущенных на Postman, я хотел бы реализовать некоторые тесты интеграции/модуля.XUnit DI через переопределенный файл автозагрузки (.net core)

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

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

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

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

Что утра Я пропустил?

+0

IIRC вы не можете использовать зависимость инъекции на тестовых классов , Вы можете разрешить xunit вводить специальные приспособления через конструктор (https://xunit.github.io/docs/shared-context.html см. Классные и коллекционные светильники). Для интеграционных тестов вам нужно получить экземпляр 'IServiceProvider' и разрешить его обслуживание. Для тестирования контроллера вы должны использовать 'TestServer' class => docs.microsoft.com/en-us/aspnet/core/testing/integration-testing – Tseng

+0

Также вы можете не захотеть наследовать от Startup.cs и вместо этого иметь отдельный класс , с кодом начальной загрузки. переопределение не работает с некоторыми конфигурациями (т. е. когда вам нужно выполнить код после A, но до B). При регистрации в течение двух раз один и тот же интерфейс может привести к исключениям, когда вы вызываете «GetRequiredService», потому что зарегистрировано более одного (если вы, следовательно, не используете 'services.TryAddXxx ()' – Tseng

+0

'Если тестовым классам необходим доступ к fixture, добавьте его как аргумент конструктора, и он будет предоставлен автоматически. 'Так что Fixtures могут использовать DI, но вы не можете DI от запуска? Ну, это позор. Ну, конечно, регистрацию дважды и т. д. можно ожидать. все же, если бы я мог сделать DI через запуск в тестах, тогда я бы прошел тест и работал в секундах. – Drakoumel

ответ

3

Вы не можете использовать инъекцию зависимостей в тестовых классах. Вы можете позволить 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); 
} 
+0

вы просто ответили на мой комментарий по моему вопросу :) Я попытаюсь реализовать это позже этим вечером , Спасибо большое, это очень поможет! – Drakoumel

+0

Как заставить «TestServer» использовать «ConfigureTestServices» вместо класса «ConfigureServices»? –

+0

@DmytroBogatov: Используйте '.UseEnvironment (" Test ")' на 'WebHostBuilder' – Tseng