【问题标题】:How to get a TestServer to use a different appsettings file?如何让 TestServer 使用不同的 appsettings 文件?
【发布时间】:2019-02-21 12:34:34
【问题描述】:

这是我遇到的一个非常奇怪的问题。我正在尝试设置 TestServer 来测试 ASP.NET API。在这个特定的测试中,我想使用不同的配置 appsettings.custom.json,而不是项目的 appsettings.json。所以,当我创建 TestServer 时,我会这样做:

protected TestServer CreateTestServer()
{
    var testConfiguration = new AspNetTestConfiguration(); // configures middleware, authentication, default/static files
    var configFile = "appsettings.custom.json";
    var config = new ConfigurationBuilder().AddJsonFile(configFile).Build();
    Container.Configure(cfg => cfg.For<IConfiguration>().Use(config)); // attempt 1 - that's my StructureMap container used for DI

    // Create the WebHostBuilder used by the test server
    var webHostBuilder = WebHost.CreateDefaultBuilder()
                                .ConfigureServices(svc =>
                                                   {
                                                       svc.AddSingleton(Container);
                                                       svc.AddSingleton(config); // attempt 2
                                                       svc.AddSingleton(testConfiguration);
                                                   })
                                .UseConfiguration(config) // attempt 3
                                .UseStartup<TestStartup>();

    return new TestServer(webHostBuilder);
}

但是,当我进入我的控制器时,通过依赖注入在构造函数中提供了一个IConfiguration,我访问了IConfiguration 中的设置,发现这些值来自appsettings.json

正如您在上面看到的,我尝试了 3 种不同的方法来说服 TestServer 它必须使用来自 appsettings.custom.json 的配置值,但无济于事。

为什么还坚持使用appsettings.json,如何解决?

更新: 越来越好奇。我尝试将 appsettings.json 重命名为 appsettings.default.json 并将其设置为我的默认设置文件。现在,神奇的是,我的TestServer 正在使用appsettings.custom.json,这是我期望的!

【问题讨论】:

  • 最好的方法是使用相同的配置文件并添加选项进行测试。
  • 我建议您使用 WebApplicationFactory for integration testing,它还支持重新配置应用程序。 – 自定义中间件和服务之类的东西,甚至是不同的启动,都是让您的测试完全不代表您的应用程序的好方法,因此您应该避免这样做。始终测试您的实际应用程序,并且仅在您需要使其作为测试工作时进行部分替换。

标签: c# asp.net-core structuremap


【解决方案1】:

确保将测试 json 文件设置为如果较新则复制到输出目录

我们在 .NET Core 2.x 中将此用于这种情况

public class TestServerFixture : IDisposable
{
    private readonly TestServer _testServer;
    public HttpClient Client { get; }

    public TestServerFixture(string assemblyName)
    {

        IWebHostBuilder builder = new WebHostBuilder()
               .UseContentRoot(Path.Combine(ConfigHelper.GetSolutionBasePath(), assemblyName))
               .UseEnvironment("Development")
               .UseConfiguration(new ConfigurationBuilder()
                    .AddJsonFile("appsettings.tests.integration.json") //the file is set to be copied to the output directory if newer
                    .Build()
                ).UseStartup(assemblyName);

        _testServer = new TestServer(builder);

        Client = _testServer.CreateClient();
    }

    public void Dispose()
    {
        Client.Dispose();
        _testServer.Dispose();
    }
}

并获得解决方案的基本路径

/// <summary>
/// Gets the current soution base path
/// </summary>
/// <returns></returns>
public static string GetSolutionBasePath()
{
    var appPath = PlatformServices.Default.Application.ApplicationBasePath;
    var binPosition = appPath.IndexOf("\\bin", StringComparison.Ordinal);
    var basePath = appPath.Remove(binPosition);

    var backslashPosition = basePath.LastIndexOf("\\", StringComparison.Ordinal);
    basePath = basePath.Remove(backslashPosition);
    return basePath;
}

【讨论】:

  • 谢谢!我正在将我的配置文件复制到输出文件夹。请参阅我对问题的更新,了解一些有趣的进展。
  • @ShaulBehr 您可能在运行测试之前没有正确地重建项目,这意味着新配置没有被复制。
  • @poke 我删除了bin 文件夹并重建了项目。没有改变行为。如果默认文件名为appsettings.json,则自定义TestServer 会尝试使用该配置。如果我将默认文件命名为其他名称,例如 appsettings.default.json,那么一切都会按预期工作。每次都重现。
  • @ShaulBehr 这是因为appsettings.json 是设置文件的名字,.NET Core 会查找,请参阅docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/…
【解决方案2】:

Daniel 的回答很好,但我有一个更简洁的解决方案,不需要明确指定使用哪个 appsettings.json 或调整路径。

需要注意的最重要的事情是TestServer使用WebHostBuilder,而不是通用的,因此你需要教这个WebHostBuilder如何从json文件、环境、机密等中读取设置,否则它不会'没办法。

这对 .NET Core 3.1+(和 .NET 5)有效,我使用与公认答案类似的方法,其中我的集成测试扩展了一个为我设置一切的抽象类。

public abstract class FunctionalTest
    : GivenAsync_WhenAsync_Then_Test
{
    private readonly IServiceProvider _serviceProvider;
    protected HttpClient HttpClient { get; }
    protected IConfiguration Configuration { get; }
    protected FunctionalTest()
    {
        var server =
            new TestServer(
                new WebHostBuilder()
                    .UseStartup<Startup>()
                    .UseCommonConfiguration()
                    .UseEnvironment("Test")
                    .ConfigureTestServices(ConfigureTestServices));

        HttpClient = server.CreateClient();
        _serviceProvider = server.Services;
        Configuration = _serviceProvider.GetService<IConfiguration>();
    }

    protected T GetService<T>() where T : class
    {
        return _serviceProvider.GetService<T>();
    }

    protected virtual void ConfigureTestServices(IServiceCollection services)
    {
        // Here replace DI container registrations for test executions, 
        // although you can also do this at the specific test since this is a virtual method that can be overriden
    }
}

有几点需要注意:

  1. GivenAsync_WhenAsync_Then_Test 只是一个自定义的简单抽象类,我用来强制我使用 Given When Then 方法编写测试。与这个问题无关,但如果你想看看它在https://gitlab.com/sunnyatticsoftware/sasw-test-support

  2. 我有一个名为UseCommonConfiguration 的自定义扩展方法,它可以完成所有繁重的工作来教IWebHostBuilder 如何读取设置。实现是标准的

    public static IWebHostBuilder UseCommonConfiguration(this IWebHostBuilder builder)
    {
       builder.ConfigureAppConfiguration((hostingContext, config) =>
       {
           var env = hostingContext.HostingEnvironment;
    
           config
               .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
               .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
               .AddEnvironmentVariables();
    
           if (env.IsDevelopment())
           {
               var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
               config.AddUserSecrets(appAssembly, optional: true);
           }
       });
    
       return builder;
    }
    
  3. 我还将环境设置为"Test",这样我只处理不同的测试执行环境,其他一切都相同(与真实应用程序相同的Startup类等)。有了这个,我告诉 AspNet 测试服务器寻找appsettings.Test.json。因此,继续使用您要使用的测试值创建一个特定的appsettings.Test.json,并将其放在测试项目的根目录下(如果需要),使用BuildAction ContentCopy to Output Directory Copy if newer(与标准appsettings.json 相同的属性或appsettings.Development.json

  4. ConfigureTestServices(sp =&gt; { /* .. */}) 行允许替换/修改在启动时发生的 DI 容器注册。用模拟或类似替换一些服务非常有用。

然后,您的测试类可以扩展这个抽象类FunctionalTest 并可以使用HttpClient 将http 请求发送到测试服务器,就像_result = await HttpClient.PostAsync("api/whatever", myStringContent); 一样

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-10
    • 2019-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-06
    相关资源
    最近更新 更多