【问题标题】:.Net Core 3.1 WebApplicationFactory TestServer: cannot find Razor view when override Startup.Net Core 3.1 WebApplicationFactory TestServer:覆盖启动时找不到 Razor 视图
【发布时间】:2020-02-04 13:19:29
【问题描述】:

我正在尝试为具有视图的控制器编写集成测试。我将其作为从 2.2 迁移到 .Net Core 3.1 的一部分。 ConfigureServices 中有很多配置需要在测试中模拟或禁用,因此,我们从现有的 Startup 类派生并覆盖所需的部分。

现在,我可以使用WebApplicationFactory 并覆盖ConfigureWebHost 使其在.Net Core 3.1 中工作。但是,我宁愿不要重写从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

我该如何修复测试?

我有一个“模拟”项目,我可以在其中重现该问题。

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();
}

【问题讨论】:

    标签: integration-testing xunit functional-testing .net-core-3.1


    【解决方案1】:

    为了解决问题,我添加了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

    【讨论】:

    • 这行得通。另请注意,在我的情况下,测试项目与被测项目 (PFWP) 位于同一父文件夹中。截至目前,它是 v5.0,如果您在 PFWP 之外拥有测试项目,您最终会遇到另一个无法解决的错误。
    • 你不知道我对这个答案有多高兴……我为此苦苦思索了好几个小时,但无济于事。谢谢!!
    猜你喜欢
    • 2018-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-30
    • 1970-01-01
    • 2020-07-08
    • 1970-01-01
    • 2017-09-15
    相关资源
    最近更新 更多