【问题标题】:Read appsettings json values in .NET Core Test Project在 .NET Core 测试项目中读取 appsettings json 值
【发布时间】:2017-02-09 00:55:41
【问题描述】:

我的 Web 应用程序需要从 appsettings.json 文件中读取 Document DB 密钥。我创建了一个带有键名的类,并将 ConfigureServices() 中的 Config 部分读取为:

public Startup(IHostingEnvironment env) {
    var builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddEnvironmentVariables();

    Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services) {
    services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
    services.AddSession();
    Helpers.GetConfigurationSettings(services, Configuration);
    DIBuilder.AddDependency(services, Configuration);
}

我正在寻找在测试项目中读取键值的方法。

【问题讨论】:

标签: c# unit-testing asp.net-core appsettings


【解决方案1】:

这是基于博客文章 Using Configuration files in .NET Core Unit Test Projects(为 .NET Core 1.0 编写的)。

  1. 在集成测试项目根目录中创建(或复制)appsettings.test.json,并在属性中将“构建操作”指定为内容,将“如果较新则复制”到输出目录。请注意,文件名(例如 appsettings.test.json )最好与普通的 appsettings.json 不同,因为如果使用相同的名称,主项目中的文件可能会覆盖测试项目中的文件。

  2. 如果尚未包含 JSON 配置文件 NuGet 包 (Microsoft.Extensions.Configuration.Json),请包含它。

  3. 在测试项目中创建一个方法,

     public static IConfiguration InitConfiguration()
             {
                 var config = new ConfigurationBuilder()
                    .AddJsonFile("appsettings.test.json")
                     .AddEnvironmentVariables() 
                     .Build();
                     return config;
             }
    

AddEnvironmentVariables(建议在 @RickStrahl blog 中)如果你想传递一些你不希望存储在 appsettings.test.json 中的秘密很有用

  1. 照常使用配置

     var config = InitConfiguration();
     var clientId = config["CLIENT_ID"]
    

顺便说一句:您也可能对将配置读入 IOptions 类感兴趣,如 Integration test with IOptions<> in .NET Core 中所述:

var options = config.Get<MySettings>();

【讨论】:

【解决方案2】:

类似于Artem answer,但使用嵌入式资源(作为流):

Stream configStream =
    Assembly.GetExecutingAssembly()
    .GetManifestResourceStream("MyNamespace.AppName.Test.appsettings.test.json");

IConfigurationRoot config = new ConfigurationBuilder()
    .AddJsonStream(configStream)
    .AddEnvironmentVariables()
    .Build();

【讨论】:

  • 一张很棒的照片,在 3 小时的挫折后帮助了我。
【解决方案3】:

添加配置文件

首先,将 appconfig.json 文件添加到集成测试项目中

配置要复制到输出的 appconfig.json 文件 通过更新目录

添加 NuGet 包

  • Microsoft.Extensions.Configuration.Json

在单元测试中使用配置

[TestClass]
public class IntegrationTests
{
    public IntegrationTests()
    {
        var config = new ConfigurationBuilder().AddJsonFile("appconfig.json").Build();
        
        _numberOfPumps = Convert.ToInt32(config["NumberOfPumps"]);

        _numberOfMessages = Convert.ToInt32(config["NumberOfMessages"]);

        _databaseUrl = config["DatabaseUrlAddress"];
    }
} 

【讨论】:

    【解决方案4】:

    我更喜欢从流而不是从文件中读取配置。这提供了更大的灵活性,因为您无需提交多个 json 配置文件即可创建轻量级测试设置:

    public static class ConfigurationHelper
    {
        public static IConfigurationRoot GetConfiguration()
        {
            byte[] byteArray = Encoding.ASCII.GetBytes("{\"Root\":{\"Section\": { ... }}");
            using var stream = new MemoryStream(byteArray);
            return new ConfigurationBuilder()
                .AddJsonStream(stream)
                .Build();
        }
    }
    

    【讨论】:

      【解决方案5】:

      Suderson 的解决方案在修改如下后对我有用:

      var builder = new ConfigurationBuilder()
          .SetBasePath(Directory.GetCurrentDirectory())
          .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
          .AddEnvironmentVariables();
      
          IConfiguration config = builder.Build();
      
          //Now, You can use config.GetSection(key) to get the config entries
      

      【讨论】:

        【解决方案6】:

        如果您正在使用 WebApplicationFactory to create a test server for integration tests 并且您已经有一种方法可以获取服务器端控制器中的配置值(您可能会这样做!),那么您可以重新使用它(并获取任何您需要的其他注入项目)在您的集成测试中,如下所示:

        // Your test fixtures would be subclasses of this
        public class IntegrationTestBase : IDisposable
        {
            private readonly WebApplicationFactory<Startup> _factory;
            protected readonly HttpClient _client;
        
            // The same config class which would be injected into your server-side controllers
            protected readonly IMyConfigService _myConfigService;
        
            // Constructor (called by subclasses)
            protected IntegrationTestBase()
            {
                // this can refer to the actual live Startup class!
                _factory = new WebApplicationFactory<Startup>();
                _client = _factory.CreateClient();
        
                // fetch some useful objects from the injection service
                _myConfigService = (IMyConfigService)_factory.Server.Host.Services.GetService(typeof(IMyConfigService));
            }
        
            public virtual void Dispose()
            {
                _client.Dispose();
                _factory.Dispose();
            }
        }
        

        请注意,在这种情况下,您不需要复制 appsettings.json,您会自动使用(测试)服务器正在使用的同一 appsettings.json

        【讨论】:

        • 嗨,迈克,我正在使用您建议的相同方法。但是我必须覆盖一些设置,我找不到办法做到这一点。有什么建议吗?
        • 嗨,这确实有道理。我只需要我的集成测试设置与我的开发设置相同。我认为appsettings.json 只支持开发、生产和登台,所以如果您需要第四个变体进行测试,我不确定。我怀疑会有一种方法可以注入一些额外的配置(因为我认为所有配置都是按顺序搜索的),这会覆盖你所需要的。
        【解决方案7】:

        对于 ASP.NET Core 2.x 项目,将 appsettings.json 文件自动复制到构建目录:

        <Project Sdk="Microsoft.NET.Sdk">
          <ItemGroup>
            <None Include="..\MyProj\appsettings.json" CopyToOutputDirectory="PreserveNewest" />
          </ItemGroup>
        </Project>
        

        【讨论】:

        • 这行得通,而且 VS 足够聪明,知道它是同一个文件。当然,您对“测试”版本所做的任何编辑都将复制到服务器版本中,因为它是同一个文件。
        【解决方案8】:

        appSettings.json 复制到您的测试项目根目录并将其属性标记为Content如果较新则复制

        var builder = new ConfigurationBuilder()
          .SetBasePath(Directory.GetCurrentDirectory())
          .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
          .AddEnvironmentVariables();
        ConfigurationManager.Configuration = builder.Build();
        

        ConfigurationManager 是一个类,它有一个静态属性Configuration。这样整个应用程序就可以以ConfigurationManager.Configuration[&lt;key&gt;] 的身份访问它

        【讨论】:

        • 前半句是对的。使用静态 ConfigurationManager.Configuration 听起来不正确。
        【解决方案9】:

        老实说,如果您是单元测试一个应用程序,您应该尝试将您正在测试的类与所有依赖项隔离开来,例如调用其他类、访问文件系统、数据库、网络等。除非您正在做集成测试或功能测试。

        话虽如此,要对应用程序进行单元测试,您可能希望从您的 appsettings.json 文件中模拟这些值,然后只测试您的逻辑。

        所以你的appsettings.json 看起来像这样。

        "DocumentDb": {
            "Key": "key1" 
        } 
        

        然后创建一个设置类。

        public class DocumentDbSettings
        {
            public string Key { get; set; }
        }
        

        然后在ConfigureServices()方法中注册。

        services.Configure<DocumentDbSettings>(Configuration.GetSection("DocumentDb"));
        

        那么例如,您的控制器/类可能看起来像这样。

        // ...
        private readonly DocumentDbSettings _settings;
        
        public HomeController(IOptions<DocumentDbSettings> settings)
        {
            _settings = settings.Value;
        }
        // ...
        public string TestMe()
        {
            return $"processed_{_settings.Key}";
        }
        

        然后在你的测试项目中你可以创建这样的单元测试类。

        public class HomeControllerTests
        {
            [Fact]
            public void TestMe_KeyShouldBeEqual_WhenKeyIsKey1()
            {
                // Arrange
                const string expectedValue = "processed_key1";
                var configMock = Substitute.For<IOptions<DocumentDbSettings>>();
                configMock.Value.Returns(new DocumentDbSettings
                {
                    Key = "key1" // Mocking the value from your config
                });
        
                var c = new HomeController(configMock);
        
                // Act
                var result = c.TestMe();
        
                // Assert
                Assert.Equal(expectedValue, result);
            }
        }
        

        我使用 NSubstitute v2.0.0-rc 进行模拟。

        【讨论】:

        • 是的,但是...如果我在做集成测试怎么办?您完全没有回答实际问题
        【解决方案10】:

        在您的测试项目的project.json 中,添加以下依赖项:

        "dependencies": {
          "xunit": "2.2.0-beta2-build3300",
          "Microsoft.AspNetCore.TestHost": "1.0.0",
          "dotnet-test-xunit": "2.2.0-preview2-build1029",
          "BancoSentencas": "1.0.0-*"
        },
        

        BancoSentencas 是我要测试的项目。其他包来自 xUnit 和将成为我们的内存服务器的 TestHost。

        还为 appsettings.json 包含此构建选项:

        "buildOptions": {
          "copyToOutput": {
            "include": [ "appsettings.Development.json" ]
          }
        }
        

        在我的测试项目中,我有以下测试类:

          public class ClasseControllerTeste : IClassFixture<TestServerFixture> {
        
            public ClasseControllerTeste(TestServerFixture fixture) {
              Fixture = fixture;
            }
        
            protected TestServerFixture Fixture { get; private set; }
        
        
            [Fact]
            public async void TestarRecuperarClassePorId() {
              using(var client = Fixture.Client) {
                var request = await Fixture.MyHttpRequestMessage(HttpMethod.Get, "/api/classe/1436");
                var response = await client.SendAsync(request);
                string obj = await response.Content.ReadAsStringAsync();
                ClasseModel classe = JsonConvert.DeserializeObject<ClasseModel>(obj);
                Assert.NotNull(classe);
                Assert.Equal(1436, classe.Id);
              }
            }
          }
        

        我还有一个 TestServerFixture 类来配置内存服务器:

          public class TestServerFixture : IDisposable {
            private TestServer testServer;
            protected TestServer TestServer {
              get {
                if (testServer == null)
                  testServer = new TestServer(new WebHostBuilder().UseEnvironment("Development").UseStartup<Startup>());
                return testServer;
              }
            }
        
            protected SetCookieHeaderValue Cookie { get; set; }
        
            public HttpClient Client {
              get {
                return TestServer.CreateClient();
              }
            }
        
            public async Task<HttpRequestMessage> MyHttpRequestMessage(HttpMethod method, string requestUri) {      
              ...
              login stuff...
              ...
              Cookie = SetCookieHeaderValue.Parse(response.Headers.GetValues("Set-Cookie").First());
        
              var request = new HttpRequestMessage(method, requestUri);
        
              request.Headers.Add("Cookie", new CookieHeaderValue(Cookie.Name, Cookie.Value).ToString());
              request.Headers.Accept.ParseAdd("text/xml");
              request.Headers.AcceptCharset.ParseAdd("utf-8");
              return request;
            }
        
            public void Dispose() {
              if (testServer != null) {
                testServer.Dispose();
                testServer = null;
              }
            }
          }
        

        这就是我测试我的项目的方式。我使用主项目中的 Startup.cs,并从我的测试项目 (appsettings.Development.json) 中的 appsettings.json 创建一个副本

        【讨论】:

        • 这个TestServer是什么?你的自定义类?
        • 这是来自Microsoft.AspNetCore.TestHost 包的类。你在使用 xUnit 吗?我将编辑我的答案并提供更多详细信息。
        • 是的。我也在使用 xUnit。
        • 感谢详细的代码。我的应用程序不是 Web API。那么请帮助我如何测试它?
        • 那么,你的应用是一个 MVC,对吧?你想测试你的 MVC 控制器吗?
        猜你喜欢
        • 1970-01-01
        • 2020-03-20
        • 2018-02-06
        • 2015-10-05
        • 2018-07-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-03
        相关资源
        最近更新 更多