WebApplicationFactory 抛出 contentRootPath 在 ASP.NET 核心集成测试中不存在的错误

WebApplicationFactory throws error that contentRootPath does not exist in ASP.NET Core integration test

我有一个 ASP.NET 核心项目,其中包含一些简单的 Razor 页面和一个 Web API 控制器。

我使用 Clean Architecture 作为起点。我重命名了项目名称,删除了 MVC 内容并添加了一些我自己的代码。一切正常运行。

但是,集成测试在调用 factory.CreateClient() 时抛出以下错误:

Test Name:  ToDo.Tests.Integration.Web.HomeControllerIndexShould.ReturnViewWithCorrectMessage
Test FullName:  ToDo.Tests.Integration.Web.HomeControllerIndexShould.ReturnViewWithCorrectMessage
Test Source:    C:\Source\Utopia\tests\ToDo.Tests\Integration\Web\HomeControllerIndexShould.cs : line 18
Test Outcome:   Failed
Test Duration:  0:00:00,001

Result StackTrace:  
at Microsoft.AspNetCore.Hosting.Internal.HostingEnvironmentExtensions.Initialize(IHostingEnvironment hostingEnvironment, String contentRootPath, WebHostOptions options)
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.BuildCommonServices(AggregateException& hostingStartupErrors)
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
   at Microsoft.AspNetCore.TestHost.TestServer..ctor(IWebHostBuilder builder, IFeatureCollection featureCollection)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateServer(IWebHostBuilder builder)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer()
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient(WebApplicationFactoryClientOptions options)
   at ToDo.Tests.Integration.Web.HomeControllerIndexShould..ctor(CustomWebApplicationFactory`1 factory) in C:\Source\Utopia\tests\ToDo.Tests\Integration\Web\HomeControllerIndexShould.cs:line 14
Result Message: 
System.ArgumentException : The content root 'C:\Source\Utopia\ToDo.Web' does not exist.
Parameter name: contentRootPath

我已经尝试使用 builder.UseContentRootbuilder.UseSolutionRelativeContentRoot 配置自定义 WebApplicationFactory,但无论我为 ContentRoot 使用什么值,它都会不断抛出相同的错误方法。

我不知道为什么我的测试失败了,而 Clean Architecture 示例中的测试正在运行。我也不知道如何解决它。

非常感谢任何指点!

我不完全确定你在说什么,但你首先不应该在测试项目中配置这样的东西。相反,您应该创建一个像 TestStartup 的 class 并从 SUT 的 Startup class 继承。在 SUT 的 Startup class 中,您应该将数据库设置等因素分解为虚拟私有方法,然后您可以在 TestStartup 上覆盖这些方法。例如,您可以创建如下方法:

private virtual void ConfigureDatabase(IServiceCollection services)
{
    services.AddDbContext<MyContext>(o =>
        o.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}

然后在您的 TestStartup 中添加如下内容:

private override void ConfigureDatabase(IServiceCollection services)
{
    var databaseName = Guid.NewGuid().ToString();
    services.AddDbContext<MyContext>(o =>
        o.UseInMemoryDatabase(databaseName));
}

然后,在设置您的工厂进行测试时,您告诉它使用您的 TestStartup:

var client = factory.WithWebHostBuilder(b => b.UseStartup<TestStartup>()).CreateClient();

或者,您可以创建自己的自定义 WebApplicationFactory 并将其设置在那里:

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<RazorPagesProject.Startup>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseStartup<TestStartup>();
    }
}

请记住,TStartup 通用类型参数用于获取入口点程序集,因此您仍然需要将 Startup 放在那里。

这里的要点是你不需要重复你所有的启动配置,然后记得保持同步。您的测试客户端将使用与实际应用程序使用的完全相同的启动配置,除了一些保留替换,例如使用内存数据库。

这个方法对我有用

        var client = _factory
            .WithWebHostBuilder(builder => builder.UseSolutionRelativeContentRoot("relative/path/of/project/under/test"))
            .CreateClient();

How the test infrastructure infers the app content root path 说(添加了我的标记)

The WebApplicationFactory constructor infers the app content root path by searching for a WebApplicationFactoryContentRootAttribute on the assembly containing the integration tests with a key equal to the TEntryPoint assembly System.Reflection.Assembly.FullName. In case an attribute with the correct key isn't found, WebApplicationFactory falls back to searching for a solution file (.sln) and appends the TEntryPoint assembly name to the solution directory. The app root directory (the content root path) is used to discover views and content files.

如果你使用的是ReSharper,很可能与版本有关。我们发现了版本 2018.3 的问题。添加 UseSolutionRelativeContentRoot 解决了它。

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
            builder.UseKestrel()
            .UseSolutionRelativeContentRoot("")
            .ConfigureAppConfiguration((context, configBuilder) =>
            {
              // Config code
            })
            .UseStartup<Startup>();
    }

WebApplicationFactory 似乎应该使用真正的 Startup class 作为参数类型:

class TestWebApplicationFactory : WebApplicationFactory<Startup>
{
     protected override IWebHostBuilder CreateWebHostBuilder()
     {
         return WebHost.CreateDefaultBuilder<TestableStartup>();
     }
}

Note that Startup is the type on true SUT code and TestableStartup is the TestingStartup configuration.

我对这个问题的解决方案是使用应用程序启动定义 WebApplicationFactory,但使用 TestStartup 设置 WebHostBuilder

示例:

public class MyApplicationFactory : WebApplicationFactory<Startup>
{
    protected override IWebHostBuilder CreateWebHostBuilder()
    {
        return WebHost.CreateDefaultBuilder();
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseStartup<TestStartup>();

        base.ConfigureWebHost(builder);
    }
}

我有一个非常简单的项目结构和 运行 进入同一个问题。项目结构:

-repo_root
 - myProject
   - myProject.csproj
   - src
 - myProjectTest
   - myProjectTest.csproj
   - tests

以上所有答案都不适合我。我浏览了 docs and other sources related to the WebApplicationFactoryContentRootAttribute to no avail. I also looked into the WebApplicationFactory implementation 并尝试在设置中使用 TEST_CONTENTROOT_APPNAME 无济于事。

最终有帮助的是在 repo_root 中创建一个空的 myProject.sln 文件。