Я пытаюсь написать интеграционный тест для контроллера с представлением. Я делаю это как часть перехода на. Net Core 3.1 из 2.2. В ConfigureServices
есть много настроек, которые нам нужно смоделировать или отключить в тестах, поэтому мы наследуем существующий класс Startup
и переопределяем необходимые детали.
Теперь я могу заставить его работать дюйма Net Core 3.1 с использованием WebApplicationFactory
и переопределением ConfigureWebHost
. Однако я скорее надеялся не переписывать существующий класс, производный от Startup
.
. Я пытался использовать подход из https://gunnarpeipman.com/aspnet-core-integration-test-startup/, где я определяю производное Startup
для WebApplicationFactory
и звоните UseSolutionRelativeContentRoot
(в котором есть UseContentRoot
, внутри которого я тоже пробовал). Тем не менее, мнения не могут быть найдены. Часть возвращенного исключения:
System.InvalidOperationException: The view 'Index' was not found. The following locations were searched:
Features\Dummy\Index.cshtml
Features\Shared\Index.cshtml
\Features\Index\Dummy.cshtml
Как я могу исправить тесты?
У меня есть «фиктивный» проект, в котором я воспроизвожу проблему.
public class Program
{
public static void Main(string[] args)
{
var host = BuildHost(args);
host.Run();
}
public static IHost BuildHost(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => webBuilder
.UseStartup<Startup>())
.Build();
}
public class Startup
{
protected virtual void AddTestService(IServiceCollection services)
{
services.TryAddSingleton<IServiceToMock, ServiceToMock>();
}
public void ConfigureServices(IServiceCollection services)
{
AddTestService(services);
services.AddMvc()
.AddFeatureFolders();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}"));
}
}
public interface IServiceToMock
{
Task DoThing();
}
public class ServiceToMock : IServiceToMock
{
public async Task DoThing() =>
throw new Exception(await Task.FromResult("service exception"));
}
[Route("candidates/[controller]/[action]")]
public class DummyController : Controller
{
private readonly IServiceToMock serviceToMock;
public DummyController(IServiceToMock serviceToMock)
{
this.serviceToMock = serviceToMock;
}
[HttpGet]
public IActionResult Index()
{
return View();
}
[HttpGet]
public async Task<bool> IsExternal(string email)
{
await serviceToMock.DoThing();
return await Task.FromResult(!string.IsNullOrWhiteSpace(email));
}
}
Index.cshtml
(идет в той же папке, что и контроллер)
@{
ViewData["Title"] = "Title";
}
<p>Hello.</p>
csproj:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OdeToCode.AddFeatureFolders" Version="2.0.3" />
</ItemGroup>
</Project>
Тестовая часть:
public class TestServerFixture : TestServerFixtureBase<Startup, TestStartup>
{
}
public class TestServerFixtureBase<TSUTStartus, TTestStartup> : WebApplicationFactory<TTestStartup>
where TTestStartup : class where TSUTStartus : class
{
private readonly Lazy<HttpClient> m_AuthClient;
public TestServerFixtureBase()
{
m_AuthClient = new Lazy<HttpClient>(() => CreateAuthClient());
}
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost.CreateDefaultBuilder()
.UseStartup<TTestStartup>();
}
public HttpClient AuthClient => m_AuthClient.Value;
protected virtual HttpClient CreateAuthClient() => WithWebHostBuilder(builder =>
{
builder.UseSolutionRelativeContentRoot("NetCore31IntegrationTests3");
builder.ConfigureTestServices(services =>
{
services.AddMvc().AddApplicationPart(typeof(TSUTStartus).Assembly);
});
}).CreateClient();
}
public class TestStartup : Startup
{
protected override void AddTestService(IServiceCollection services)
{
services.AddSingleton<IServiceToMock, TestServiceToMock>();
}
}
public class TestServiceToMock : IServiceToMock
{
public async Task DoThing() => await Task.CompletedTask;
}
public class HomeControllerTests : IClassFixture<TestServerFixture>
{
private readonly TestServerFixture _factory;
public HomeControllerTests(TestServerFixture factory)
{
_factory = factory;
}
[Theory]
[InlineData("/candidates/dummy/IsExternal?email=aaa")]
[InlineData("/candidates/dummy/index")]
[InlineData("candidates/dummy/index")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.AuthClient;
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode();
}
}
Рабочее исправление Я пытаюсь избежать:
public class TestServerFixtureBase<TSUTStartus, TTestStartup> : WebApplicationFactory<TSUTStartus>
where TTestStartup : class where TSUTStartus : class
{
private readonly Lazy<HttpClient> m_AuthClient;
public TestServerFixtureBase()
{
m_AuthClient = new Lazy<HttpClient>(() => CreateAuthClient());
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(IServiceToMock));
if (descriptor != null)
services.Remove(descriptor);
services.AddSingleton<IServiceToMock, TestServiceToMock>();
});
}
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost.CreateDefaultBuilder()
.UseStartup<TSUTStartus>();
}
public HttpClient AuthClient => m_AuthClient.Value;
protected virtual HttpClient CreateAuthClient() => WithWebHostBuilder(builder => { }).CreateClient();
}