Я пытаюсь написать интеграционный тест ASP.NET Core 2.2, в котором настройка теста украшает конкретную службу, которая обычно будет доступна API как зависимость.Декоратор дал бы мне некоторые дополнительные возможности, которые мне понадобились бы в моих интеграционных тестах для перехвата вызовов к базовому сервису, но Я не могу правильно оформить обычный сервис в ConfigureTestServices
, как мой текущийпрограмма установки выдаст мне:
Исключение типа 'System.InvalidOperationException' произошло в Microsoft.Extensions.DependencyInjection.Abstractions.dll, но не было обработано в коде пользователя
Нет службы длятип 'Foo.Web.BarService' зарегистрирован.
Чтобы воспроизвести это, я только что использовал VS2019 для создания нового проекта ASP.NET Core 2.2 API Foo.Web
...
// In `Startup.cs`:
services.AddScoped<IBarService, BarService>();
public interface IBarService
{
string GetValue();
}
public class BarService : IBarService
{
public string GetValue() => "Service Value";
}
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IBarService barService;
public ValuesController(IBarService barService)
{
this.barService = barService;
}
[HttpGet]
public ActionResult<string> Get()
{
return barService.GetValue();
}
}
... и сопутствующий проект xUnit Foo.Web.Tests
I использует WebApplicationfactory<TStartup>
...
public class DecoratedBarService : IBarService
{
private readonly IBarService innerService;
public DecoratedBarService(IBarService innerService)
{
this.innerService = innerService;
}
public string GetValue() => $"{innerService.GetValue()} (decorated)";
}
public class IntegrationTestsFixture : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(di.GetRequiredService<BarService>()));
});
}
}
public class ValuesControllerTests : IClassFixture<IntegrationTestsFixture>
{
private readonly IntegrationTestsFixture fixture;
public ValuesControllerTests(IntegrationTestsFixture fixture)
{
this.fixture = fixture;
}
[Fact]
public async Task Integration_test_uses_decorator()
{
var client = fixture.CreateClient();
var result = await client.GetAsync("/api/values");
var data = await result.Content.ReadAsStringAsync();
result.EnsureSuccessStatusCode();
Assert.Equal("Service Value (decorated)", data);
}
}
Такое поведение имеет смысл, или, по крайней мере, я думаю это делает: я полагаю, что маленькая заводская лямбда-функция (di => new DecoratedBarService(...)
) в ConfigureTestServices
не может получить конкретный BarService
из контейнера di
, потому что он находится в основном сервисном столбцелекция, а не в службах тестирования.
Как сделать так, чтобы контейнер ASP.NET Core DI по умолчанию предоставлял экземпляры декоратора с исходным конкретным типом в качестве внутренней службы?
ПопыткаРешение 2:
Я пробовал следующее:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(Server.Host.Services.GetRequiredService<BarService>()));
});
}
Но это неожиданно сталкивается с той же проблемой.
Попытка решения 3:
Вместо этого запрашивается IBarService
, например:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(Server.Host.Services.GetRequiredService<IBarService>()));
});
}
Дает мне другую ошибку:
СистемаИсключениев моем маленьком репрое, как это:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(new BarService()));
});
}
Но это больно много в моем актуальном приложении, потому что BarService
не имеет простого конструктора без параметров: он имеет умеренно сложный граф зависимостей, поэтому я действительно хотел бы разрешить экземпляры из Startup
DI контейнер.
PS.Я пытался сделать этот вопрос полностью автономным, но для вашего удобства также имеется повторный клон и беги .