如何让TestServer使用其他appsettings文件?

时间:2019-02-21 12:34:34

标签: c# asp.net-core structuremap

这是我遇到的一个很奇怪的问题。我正在尝试设置TestServer来测试ASP.NET API。在此特定测试中,我想使用不同的配置appsettings.json而不是项目的appsettings.custom.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中的设置,发现值是{ {1}}!

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

为什么它仍然坚持使用appsettings.custom.json,我该如何解决?

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

2 个答案:

答案 0 :(得分:0)

确保您设置了要更新的测试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;
}

答案 1 :(得分:0)

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.jsonappsettings.Development.json

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

然后,您的测试类可以扩展此抽象类 FunctionalTest 并使用 HttpClient 向测试服务器发送 http 请求,如 _result = await HttpClient.PostAsync("api/whatever", myStringContent);