Переконфигурировать зависимости при тестировании интеграции ASP.NET Core Web API и EF Core
Я следую этому уроку
Интеграционное тестирование с Entity Framework Core и SQL Server
Мой код выглядит так
Интеграционный тестовый класс
public class ControllerRequestsShould : IDisposable
{
private readonly TestServer _server;
private readonly HttpClient _client;
private readonly YourContext _context;
public ControllerRequestsShould()
{
// Arrange
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
var builder = new DbContextOptionsBuilder<YourContext>();
builder.UseSqlServer($"Server=(localdb)\\mssqllocaldb;Database=your_db_{Guid.NewGuid()};Trusted_Connection=True;MultipleActiveResultSets=true")
.UseInternalServiceProvider(serviceProvider);
_context = new YourContext(builder.Options);
_context.Database.Migrate();
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.UseEnvironment(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")));
_client = _server.CreateClient();
}
[Fact]
public async Task ReturnListOfObjectDtos()
{
// Arrange database data
_context.ObjectDbSet.Add(new ObjectEntity{ Id = 1, Code = "PTF0001", Name = "Portfolio One" });
_context.ObjectDbSet.Add(new ObjectEntity{ Id = 2, Code = "PTF0002", Name = "Portfolio Two" });
// Act
var response = await _client.GetAsync("/api/route");
response.EnsureSuccessStatusCode();
// Assert
var result = Assert.IsType<OkResult>(response);
}
public void Dispose()
{
_context.Dispose();
}
Насколько я понимаю, метод .UseStartUp
гарантирует, что TestServer
использует мой класс запуска
У меня проблема в том, что, когда мой закон акт
var response = await _client.GetAsync("/api/route");
Я получаю сообщение об ошибке в моем классе запуска, что строка подключения пуста. Я думаю, что мое понимание проблемы состоит в том, что, когда мой контроллер получает доступ от клиента, он внедряет мой репозиторий данных, который, в свою очередь, вводит контекст БД.
Я думаю, что мне нужно настроить сервис как часть new WebHostBuilder
раздела new WebHostBuilder
чтобы он использовал контекст, созданный в тесте. Но я не уверен, как это сделать.
Метод ConfigureServices в Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services
services.AddMvc(setupAction =>
{
setupAction.ReturnHttpNotAcceptable = true;
setupAction.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
setupAction.InputFormatters.Add(new XmlDataContractSerializerInputFormatter());
});
// Db context configuration
var connectionString = Configuration["ConnectionStrings:YourConnectionString"];
services.AddDbContext<YourContext>(options => options.UseSqlServer(connectionString));
// Register services for dependency injection
services.AddScoped<IYourRepository, YourRepository>();
}
Ответы
Ответ 1
Вот два варианта:
1. Используйте WebHostBuilder.ConfigureServices
Через некоторое время я думаю, что самое простое решение - использовать WebHostBuilder.ConfigureServices
вместе с WebHostBuilder.UseStartup<T>
чтобы переопределить и WebHostBuilder.UseStartup<T>
регистрации DI веб-приложения:
_server = new TestServer(new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddScoped<IFooService, MockService>();
})
.UseStartup<Startup>()
);
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//use TryAdd to support mocking the service
services.TryAddTransient<IFooService, FooService>();
}
}
Ключевым моментом здесь является использование методов TryAdd
внутри исходного класса Startup
. Пользовательское WebHostBuilder.ConfigureServices
вызывается перед Startup
, так что вы зарегистрировать издеваетесь, прежде чем реальные услуги. TryAdd
пропускает регистрацию, если тот же интерфейс уже зарегистрирован, поэтому реальные сервисы не будут установлены.
Дополнительная информация: Запуск интеграционных тестов для основных приложений ASP.NET.
2. Наследование/новый класс Startup
Создайте класс TestStartup
для повторной настройки ASP.NET Core DI. Вы можете унаследовать его от Startup
и переопределить только необходимые методы:
public class TestStartup : Startup
{
public TestStartup(IHostingEnvironment env) : base(env) { }
public override void ConfigureServices(IServiceCollection services)
{
//mock DbContext and any other dependencies here
}
}
Кроме того, TestStartup
может быть создан с нуля, чтобы сохранить TestStartup
тестирования.
И укажите его в UseStartup
для запуска тестового сервера:
_server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>());
Полный пример: интеграционное тестирование вашего основного приложения asp.net с базой данных в памяти.
Ответ 2
@ilya-chumakov ответ потрясающий. Я просто хотел бы добавить еще один вариант
3. Используйте метод ConfigureTestServices из WebHostBuilderExtensions.
Метод ConfigureTestServices доступен в Microsoft.AspNetCore.TestHost версии 2.1 (20.05.2018 это RC1-финал). И это позволяет нам переопределять существующие регистрации с помощью макетов.
Код:
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureTestServices(services =>
{
services.AddTransient<IFooService, MockService>();
})
);