Переконфигурируйте зависимости при тестировании интеграции 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
раздел, чтобы он использовал контекст, созданный в тесте. Но я не уверен, как это сделать.
Метод 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>();
}
3 ответа
Ответ @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>();
})
);
1. Используйте WebHostBuilder.ConfigureServices
Через некоторое время я думаю, что самым простым решением является использование WebHostBuilder.ConfigureServices
вместе с 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
может быть создан с нуля, чтобы держать тестирование чище.
И указать это в UseStartup
запустить тестовый сервер:
_server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>());
Завершите большой пример: интеграционное тестирование вашего основного приложения asp.net с базой данных в памяти.
@Илья-Чумаков @lilo.jacob, оба ответа находятся на правильном пути, но я хотел бы добавить последнее дополнение, которое сделало бы ответ совершенно идеальным.
Если у вас есть
.ConfigureServices(services =>
{
serviceCollection
.Replace(ServiceDescriptor.Scoped(typeof(IFooService), a => MockService));
}
)
Таким образом, если IFooService уже добавлен при запуске, он будет удален и заменен. А если это не так, он будет добавлен.