.Net Core 3.1 WebApplicationFactory TestServer:覆盖启动时找不到 Razor 视图
.Net Core 3.1 WebApplicationFactory TestServer: cannot find Razor view when override Startup
我正在尝试为带有视图的控制器编写集成测试。我这样做是从 2.2 迁移到 .Net Core 3.1 的一部分。 ConfigureServices
中有很多配置我们需要在测试中模拟或禁用,因此,我们从现有的 Startup
class 派生并覆盖所需的部分。
现在,我可以使用 WebApplicationFactory
并覆盖 ConfigureWebHost
使其在 .Net Core 3.1 中工作。但是,我更希望不要重写现有的 class 派生自 Startup
.
我尝试使用 https://gunnarpeipman.com/aspnet-core-integration-test-startup/ 中的方法,其中我为 WebApplicationFactory
指定派生的 Startup
并调用 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
如何修复测试?
我有一个 "mock" 项目重现了这个问题。
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();
}
为了解决问题 WithWebHostBuilder
我添加了
services
.AddMvc()
.AddRazorRuntimeCompilation()
.AddApplicationPart(typeof(TTestStartup).Assembly);
但是,还有其他修复建议,例如:
var builder = new WebHostBuilder();
builder.ConfigureAppConfiguration((context, b) => {
context.HostingEnvironment.ApplicationName = typeof(HomeController).Assembly.GetName().Name;
});
来自 https://github.com/dotnet/aspnetcore/issues/17655#issuecomment-581418168
我正在尝试为带有视图的控制器编写集成测试。我这样做是从 2.2 迁移到 .Net Core 3.1 的一部分。 ConfigureServices
中有很多配置我们需要在测试中模拟或禁用,因此,我们从现有的 Startup
class 派生并覆盖所需的部分。
现在,我可以使用 WebApplicationFactory
并覆盖 ConfigureWebHost
使其在 .Net Core 3.1 中工作。但是,我更希望不要重写现有的 class 派生自 Startup
.
我尝试使用 https://gunnarpeipman.com/aspnet-core-integration-test-startup/ 中的方法,其中我为 WebApplicationFactory
指定派生的 Startup
并调用 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
如何修复测试?
我有一个 "mock" 项目重现了这个问题。
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();
}
为了解决问题 WithWebHostBuilder
我添加了
services
.AddMvc()
.AddRazorRuntimeCompilation()
.AddApplicationPart(typeof(TTestStartup).Assembly);
但是,还有其他修复建议,例如:
var builder = new WebHostBuilder();
builder.ConfigureAppConfiguration((context, b) => {
context.HostingEnvironment.ApplicationName = typeof(HomeController).Assembly.GetName().Name;
});
来自 https://github.com/dotnet/aspnetcore/issues/17655#issuecomment-581418168