Read appsettings json values in .NET Core Test Project
Asked Answered
I

10

152

My Web application needs to read the Document DB keys from appsettings.json file. I have created a class with the key names and reading the Config section in ConfigureServices() as:

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

I'm looking for the ways to read the Key values in Test project.

Inning answered 30/9, 2016 at 12:32 Comment(1)
docs.asp.net/en/latest/fundamentals/…Biological
A
213

This is based on the blog post Using Configuration files in .NET Core Unit Test Projects (written for .NET Core 1.0).

  1. Create (or copy) the appsettings.test.json in the Integration test project root directory, and in properties specify "Build Action" as Content and "Copy if newer" to Output Directory. Note that it’s better to have file name (e.g. appsettings.test.json ) different from normal appsettings.json, because it is possible that a file from the main project override the file from the test project, if the same name will be used.

  2. Include the JSON Configuration file NuGet package (Microsoft.Extensions.Configuration.Json) if it's not included yet.

  3. In the test project create a method,

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

AddEnvironmentVariables (suggested in @RickStrahl blog ) is useful if you want to pass some secrets, that you prefer not store in appsettings.test.json

  1. Use the configuration as usual

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

BTW: You also may be interesting in reading the configuration into the IOptions class as described in Integration test with IOptions<> in .NET Core:

var options = config.Get<MySettings>();
Agee answered 1/12, 2017 at 10:45 Comment(3)
config.Get<MySettings>() returns empty value. You should use IOptions like this; #46020488Calaverite
applies to Net 6 MSTest project in VS 2022 ?Premonition
@Kiquenet, yes, we are using this approach in .Net 6 MsTest projects. I didn’t look if anything better exist in more recent versions.Agee
A
46

Add the configuration file

First, add a appconfig.json file to the Integration test project

Configure the appconfig.json file to be copied to the output directory by updating

enter image description here

Add NuGet package

  • Microsoft.Extensions.Configuration.Json

Use the configuration in your unit tests

[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"];
    }
} 
Alonsoalonzo answered 30/12, 2019 at 16:2 Comment(3)
valid for NET 6 using Section: (builder.Configuration.GetSection( and IOptions?Premonition
Step 1b: Make sure json file's properties are set to: Copy to Output Directory: Copy AlwaysGust
Is there a way to get appsettings.Developlent.json from webApi project inside of appsettings.json?Swoosh
B
26

Suderson's solution worked for me when modified as below:

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
Baroque answered 1/1, 2019 at 20:19 Comment(0)
W
6

For ASP.NET Core 2.x projects, copy the appsettings.json file to the build dir automatically:

<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <None Include="..\MyProj\appsettings.json" CopyToOutputDirectory="PreserveNewest" />
  </ItemGroup>
</Project>
Watchmaker answered 5/4, 2019 at 14:52 Comment(2)
This works and VS is smart enough to know that it's the same file. Of course, any edits you make to your 'test' version will be replicated into the server version, since it's the same file.Elvera
I like this solution. In my implementation I copied the appsettings.development.json as I don't want to be messing with the real deal in a test project. Better than manually copying everytime you need to update the file!Prognathous
M
4

Copy the appSettings.json to your Test project root directory and mark its property as Content and Copy if newer.

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

ConfigurationManager is a class and it has a static property Configuration. This way the whole application can just access it as ConfigurationManager.Configuration[<key>]

Misestimate answered 5/7, 2017 at 20:0 Comment(1)
First half is right. Using static ConfigurationManager.Configuration doesn't sound correct.Agee
I
4

Similar to Artem answer, but using an embedded resource (as stream):

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

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

enter image description here

Intercourse answered 25/8, 2020 at 16:23 Comment(1)
a great picture that helped me after 3 hours of frustration.Lilith
M
2

I prefer to read configuration from a stream rather than from a file. That gives more flexibility because you can create a light weight test setup without committing multiple json configuration files:

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();
    }
}
Mitrailleuse answered 7/6, 2020 at 16:39 Comment(0)
N
0

In the project.json from you test project, add the following dependencies:

"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 is the project I want to test. The other packages are from xUnit and the TestHost that will be our in-memory server.

Include also this build option for the appsettings.json:

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

In my test project, I have the following test class:

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

And I also have the TestServerFixture class that will configure the in-memory server:

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

That's how I test my project. I use the Startup.cs from the main project, and I create a copy from the appsettings.json in my test project (appsettings.Development.json)

Nobles answered 30/9, 2016 at 12:49 Comment(8)
What's this TestServer? Your custom class?Inning
It's a class from Microsoft.AspNetCore.TestHost package. Are you using xUnit? I'm gonna edit my answer and provide more details.Nobles
Yeah. I'm also using xUnit.Inning
Thanks for the detailed code. My application is not a Web API. So help me how can I test it?Inning
So, you app is a MVC, right? Do you want to test your MVC Controller?Nobles
You can use the same code and pass the URL from your MVC Controller. Replace the '/api/classe/1436' for '/home/youraction'. If you're action return a view, then you need this view in your test project too. But I would not test a view this way.Nobles
Another answer that ignores the question :(Helico
Need help? The answer shows how to use app.settings in the test project.Nobles
G
0

If you are using WebApplicationFactory to create a test server for integration tests and you already have a way to get at config values in your server-side controllers (you probably do!), then you can just re-use this (and get at any other injected items you need) in your integration tests, as follows:

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

Note that you do not need to copy over appsettings.json in this case, you're automatically using the same appsettings.json which the (test) server is using.

Glyptic answered 6/6, 2019 at 16:5 Comment(2)
Hi Mike, I'm using the same method you suggested. But I have to override some settings, I couldn't find a way to make that. Any suggestions?Huth
Hi, That does make sense. I only need my integration test settings to be the same as my development settings. I think appsettings.json only supports Development, Production and Staging, so if you need a fourth variant for Test, I am not sure. I suspect there would be a way to inject some extra config (as I think all config is searched in order) that would override what you need.Glyptic
V
-5

Honestly, if you are unit testing an application, you should try to isolate the class you are testing from all dependencies, like calling other classes, accessing file system, database, network etc. Unless you are doing integration testing or functional testing.

Having that said, to unit test the application, you probably want to mock these values from your appsettings.json file, and just test your logic.

So your appsettings.json would look like this.

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

Then create a settings class.

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

Then register it in ConfigureServices() method.

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

Then for example your controller/class could look like this.

// ...
private readonly DocumentDbSettings _settings;

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

Then in your tests project you can create such unit test class.

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

I used NSubstitute v2.0.0-rc for mocking.

Vandiver answered 30/9, 2016 at 16:8 Comment(1)
Yeah but... what if I'm doing integration tests? You completely failed to answer the actual questionHelico

© 2022 - 2024 — McMap. All rights reserved.