在.NET Core测试项目中读取appsettings JSON值

146

我的Web应用程序需要从appsettings.json文件中读取Document DB键。我创建了一个包含键名称的类,并在ConfigureServices()中读取配置部分:

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

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


1
https://docs.asp.net/en/latest/fundamentals/configuration.html#configuration - Nkosi
10个回答

203

这是基于博客文章在.NET Core单元测试项目中使用配置文件(写于.NET Core 1.0)。

  1. 在集成测试项目的根目录中创建(或复制)appsettings.test.json,并在属性中指定“生成操作”为Content,“如果较新则复制”到输出目录。请注意,最好将文件名(例如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的博客中建议使用),如果您想传递一些不希望存储在appsettings.test.json文件中的机密信息,则非常有用。

  1. 像往常一样使用配置

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

顺便说一下:你可能会对阅读如下链接中描述的将配置读入IOptions类中的方法感兴趣:在.NET Core中使用IOptions<>进行集成测试:

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

4
config.Get<MySettings>() 返回空值。您应该像这样使用 IOptions;https://dev59.com/KlYO5IYBdhLWcg3wU_6M - kaya
适用于在VS 2022中使用Net 6 MSTest项目吗? - Kiquenet
1
@Kiquenet,是的,我们在.Net 6 MsTest项目中使用这种方法。我没有查看是否在更近期的版本中存在更好的方法。 - Michael Freidgeim

43

添加配置文件

首先,将 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"];
    }
} 

1
使用 Section: (builder.Configuration.GetSection( 和 IOptions?,适用于 NET 6。 - Kiquenet
1
步骤1b:确保JSON文件的属性设置为:“复制到输出目录:始终复制”。 - freedomn-m
有没有一种方法可以从WebApi项目中获取appsettings.Development.json并将其放入appsettings.json中? - Vagner Wentz

26

当按以下方式修改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

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

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

这个可以运行,而且VS聪明到知道它是同一个文件。当然,你对“test”版本所做的任何编辑都会被复制到服务器版本中,因为它们是同一个文件。 - keithl8041
我喜欢这个解决方案。在我的实现中,我复制了appsettings.development.json文件,因为我不想在测试项目中搞砸真正的文件。比每次需要更新文件时手动复制要好! - AsPas

4

appSettings.json 复制到您的测试项目根目录,并将其属性标记为 ContentCopy if newer

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

ConfigurationManager 是一个类,它有一个静态属性 Configuration。这样整个应用程序只需将其访问为 ConfigurationManager.Configuration[<key>]


5
前半部分正确。使用静态 ConfigurationManager.Configuration 不太准确。 - Michael Freidgeim

4
Artem的回答类似,但是使用嵌入式资源(作为流):
Stream configStream =
    Assembly.GetExecutingAssembly()
    .GetManifestResourceStream("MyNamespace.AppName.Test.appsettings.test.json");

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

enter image description here


1
一张很棒的图片,在我沮丧了三个小时之后帮助了我。 - Bashir Momen

2
我更喜欢从流中读取配置而不是从文件中读取。这样更灵活,因为您可以创建一个轻量级的测试设置,而无需提交多个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();
    }
}

0
如果你正在使用WebApplicationFactory创建用于集成测试的测试服务器,并且您已经有一种方法来获取服务器端控制器中的配置值(您可能已经有了!),那么您可以按照以下方式在集成测试中重复使用此方法(并获得任何其他所需的注入项):
// 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文件。

1
嗨,Mike,我正在使用你建议的方法。但是我必须覆盖一些设置,我找不到方法来实现。有什么建议吗? - fkucuk
嗨,那确实有道理。我只需要我的集成测试设置与开发设置相同。我认为 appsettings.json 只支持开发、生产和分段,所以如果你需要第四个变体进行测试,我不确定。我猜想会有一种方法注入额外的配置(因为我认为所有配置都是按顺序搜索的),可以覆盖您所需的内容。 - MikeBeaton

0
在你的测试项目的 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.json的一个副本(appsettings.Development.json)。

这个TestServer是什么?你自定义的类吗? - S.Siva
这是来自 Microsoft.AspNetCore.TestHost 包的一个类。你在使用 xUnit 吗?我会编辑我的答案并提供更多细节。 - Fabricio Koch
是的,我也在使用xUnit。 - S.Siva
感谢您提供详细的代码。我的应用程序不是Web API,那么请问如何测试它呢? - S.Siva
所以,你的应用程序是MVC架构的,对吧?你想测试一下你的MVC控制器吗? - Fabricio Koch
显示剩余3条评论

-5
老实说,如果你正在进行应用程序的单元测试,你应该尝试将你正在测试的类与所有依赖项隔离开来,比如调用其他类、访问文件系统、数据库、网络等。除非你正在进行集成测试或功能测试。
话虽如此,为了对应用程序进行单元测试,你可能想要从你的 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 进行模拟。


34
好的,如果我正在进行集成测试怎么办?你完全没有回答实际问题。 - Joe Phillips

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接