Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Accessing Configuration in .NET Core Test Projects


On this page:

If you've been following my blog you know I've written a bit about how the configuration system works in .NET Core and specifically in ASP.NET Core using the dependency injection system:

Both posts describe how you can set up configuration using the various now automatically configured Configuration services in the ASP.NET Startup class and its ConfigureServices() method.

But how do you do this in a Test or any other ASP.NET Core project where configuration isn't automatically configured?

Why Configuration? My simple Use Case - Sensitive Values

When running test projects it's often possible to get away without having to configure a configuration class and just provide explicit values. But often you actually need full dependency injection to be hooked up in order to get Configuration injected into dependencies which brings up two issues:

  • How to get access to the Configuration Provider
  • Hooking up Dependency Injection so Configuration can be injected

My first use case is simple and doesn't require dependency injection: I simply need configuration to handle reading some configuration information in order to test sending an email to check out a new mail provider. I explicitly need to make it so I don't hardcode the sensitive email values and they don't end up in my Git repo. So it would be nice to use UserSecrets as well as get the values from the already existing configuration object config in appSettings.json - same as the Web application that actually runs this code.

The second scenario involves using a business object that also uses this email sending logic in an integration test. Here the configuration object is injected into the business object so I need to have dependency injection available.

Let's take a look at both of these scenarios.

IConfiguration in non-ASP.NET Projects

ASP.NET Core 2.0 now automatically provides an IConfiguration provider that handles input from appsettings.json (including the .Development file), UserSecrets and Environment variable which is great. Configuration is such a core thing that almost every application needs it and with ASP.NET Core 2.0 you don't have to worry about setting up the configuration system manually.

However in a test project that onus falls on you. Unfortunately it's not quite so easy to do this as in ASP.NET because test projects don't automatically configure either a Dependency injection container with common objects, or a configuration provider, so this has to be handled manually.

Fortunately the process to do this is pretty straight forward.

Setting up and Retrieving a Raw Configuration Object

In my test projects I generally add a TestHelper class that provides a few commonly used values, but I also add a few helper methods and one of the methods I typically create is a GetApplicationConfiguration() class. In this application I have a configuration class call KavaDocsConfiguration which is a nested class that contains a bunch of values along with a nested Email object that contains the configuration values I need for my mail test code.

Here's what my configuration in appsettings.json looks like:

// appsettings.json
{
  "Logging": {...},
  "KavaDocs": {
    "ApplicationName": "KavaDocs",
    "ConnectionString": null,    
    "ApplicationBasePath": "/",
    "ApplicationHomeUrl": "https://localhost:5000",
    "Email": {
      "MailServer": null,
      "MailServerUsername": null,
      "MailServerPassword": null,
      "SenderName": "Kava Docs Administration",
      "SenderEmail": "support@kavadocs.com",
      "AdminSenderEmail": "support@kavadocs.com",
      "UseSsl": true
    }
  }
}

The UserSecrets JSON data then overrides the sensitive values that are stored outside of the project root so they don't get checked into Git. The file is secrets.json in the local user's User Secrets location:

{
  "KavaDocs": {
    "ConnectionString": "server=.;database=kavadocs;integrated security=true;",
    "Email": {
      "MailServer": "smtp.mailserver.org",
      "MailServerUsername": "seekrity@darko.com",
      "MailServerPassword": "123456c37a623c686f04ab654321",
      "UseSsl": true
    }
  } 
}

To access the configuration I have to build an IConfigurationRoot explicitly, which is the part that ASP.NET handles explicitly. Once I have the config root I can then bind it to an object instance.

Here are a couple of helpers that configure configuration root and provide an instance of a configuration object - we'll use both of these methods for different purposes later:

public static IConfigurationRoot GetIConfigurationRoot(string outputPath)
{            
    return new ConfigurationBuilder()
        .SetBasePath(outputPath)
        .AddJsonFile("appsettings.json", optional: true)
        .AddUserSecrets("e3dfcccf-0cb3-423a-b302-e3e92e95c128")
        .AddEnvironmentVariables()
        .Build();
}

public static KavaDocsConfiguration GetApplicationConfiguration(string outputPath)
{
    var configuration = new KavaDocsConfiguration();

    var iConfig = GetIConfigurationRoot(outputPath);

    iConfig
        .GetSection("KavaDocs")
        .Bind(configuration);

    return configuration;
}

Notice that the code needs a basepath in order to find the appsettings.json file which is going to be the output path for the file in the test project. I copied this file from my Web Project so I get the same configuration settings and then make sure I mark it as copy to the output folder:

In order for UserSecrets to work in a test project a little extra effort is required since test projects don't let you just edit the value in Visual Studio as you can in a Web Project. I added my UserSecrets key from the Web project into test project's .csproj file configuration:

<PropertyGroup>
  <TargetFramework>netcoreapp2.0</TargetFramework>
  <UserSecretsId>e4ddcccf-0cb3-423a-b302-e3e92e95c128</UserSecretsId>
</PropertyGroup>

so now I'm also looking at the same UserSecrets values that my Web project is looking at. Yay!

Using the Configuration Object Explicitly

In my test project using NUnit I can now pull this value out as part of the initialization and store it as a property on my test object:

[TestFixture]
public class SmtpTests
{
    private KavaDocsConfiguration configuration;

    [SetUp]
    public void Init()
    {
        configuration = TestHelper.GetApplicationConfiguration(TestContext.CurrentContext.TestDirectory);
    }
    
    [Test]
    public async void SendEmailTest()
    {
        var smtp = new SmtpClientNative();

        // this code here uses the configuration
        smtp.MailServer = configuration.Email.MailServer;
        smtp.Username = configuration.Email.MailServerUsername; 
        smtp.Password = configuration.Email.MailServerPassword; 

        smtp.SenderEmail = "West Wind Technologies <info@west-wind.com>";
        smtp.Recipient = "test@gmail.com";

        smtp.Message = "Hello from Mail Gun. This is a test";
        smtp.Subject = "Mailgun Test Message";

        Assert.IsTrue(await smtp.SendMailAsync(),smtp.ErrorMessage);
    }

}    

The test method then uses the configuration values and I'm off to the races. The values are read from both appSettings.json and from UserSecrets.

This works great and if all you need is a configuration object to read a few values this approach is easy and sufficient.

Setting up Dependency Injection

For the second use case I mentioned in the intro I need Configuration to come from Dependency injection in order to inject it into child objects in the dependency chain. To do this I need to a little more work setting up the Dependency provider in the configuration. The business object in this case has a number of dependencies on a EF DbContext as well as the configuration.

In order to do this I can set up the dependency injection in the initialization of the class:

public class SmtpTests 
{
    private ServiceProvider serviceProvider;
    private KavaDocsConfiguration configuration;        
    private UserBusiness userBusiness;
    
    [SetUp]
    public void Init()
    {
       configuration = TestHelper.GetApplicationConfiguration(TestContext.CurrentContext.TestDirectory);
    
       var services = new ServiceCollection();
    
       // Simple configuration object injection (no IOptions<T>)
       services.AddSingleton(configuration);
       
    
       // configure EF Core DbContext - using the configuration
       services.AddDbContext<KavaDocsContext>(builder =>
       {
           var connStr = configuration.ConnectionString;
           if (string.IsNullOrEmpty(connStr))
               connStr = "server=.;database=KavaDocs; integrated security=true;MultipleActiveResultSets=true";
    
           builder.UseSqlServer(connStr, opt =>
           {
               opt.EnableRetryOnFailure();
               opt.CommandTimeout(15);
           });
       });
       
       // has a depedency on DbContext and Configuration
       services.AddTransient<UserBusiness>();
    
       // Build the service provider
       serviceProvider = services.BuildServiceProvider();
       
       // create a userBusiness object with DI    
       userBusiness = serviceProvider.GetRequiredService<UserBusiness>();
    }
}

The code creates a services collection and adds the various dependencies needed for this particular test class. If you end up doing this for a bunch of classes this configuration code could also be moved into the test helper which could return an object with all the dependencies.

So this code adds the configuration, a DbContext, and a business object into the service provider.

In my business object I have a method that handles the email sending I showed earlier internally and I can now load a user and run an integration test sending a validation key:

[Test]
public void UserSendEmail()
{
    // connection string should be set from config
    var user = userBusiness.GetUser(TestHelper.UserId1);
    var validationKey = user.ValidationKey;

    Assert.IsTrue(userBusiness.ValidateEmail(validationKey));
}

And there you have it - injected values in your tests.

IOptions instead of raw Configuration

If you'd rather inject IOptions<T> rather than the raw configuration instance you can change the Init() code slightly and use the following:

var services = new ServiceCollection();

// IOption configuration injection
services.AddOptions();

var configurationRoot = TestHelper.GetIConfigurationRoot(TestContext.CurrentContext.TestDirectory);
services.Configure<KavaDocsConfiguration>(configurationRoot.GetSection("KavaDocs"));
...

serviceProvider = services.BuildServiceProvider();

// to use (or store in )
iConfig = serviceProvider.GetRequiredService<IOptions<KavaDocsConfiguration>>()
var server = iConfig.Value.Email.MailServer;

Usually I try to avoid IOptions<T> for the sheer ugliness of the intermediate interface, and unless you need the specific features of IOptions<T> (see my previous article) I much rather just use the raw configuration object.

Summary

Using Configuration in non-ASP.NET projects is not real obvious or at least it wasn't for me so hopefully this post provides a simple overview on how you can get the same configuration you might be using in your main application to also work inside of your test or other non-ASP.NET Projects.

this post created and published with Markdown Monster
Posted in .NET Core  ASP.NET Core  

The Voices of Reason


 

Kevin Rich
February 19, 2018

# re: Accessing Configuration in .NET Core Test Projects

This is great. I was goofing with this issue just last week, but hadn't gotten around to flushing out a solution. Which packages (or metapackages) are you using in your test project for the configurations? Microsoft.Extensions.Configuration doesn't seem to cover all of the extensions methods for the ConfigurationBuilder.


Jav
February 19, 2018

# re: Accessing Configuration in .NET Core Test Projects

Thanks. Useful info for integration test scenarios (as per you example) but worth pointing out (to others) that this should not be necessary in unit tests and if you need config files and DI for these kinds of tests then you are probably doing something wrong.


Dmitry Pavlov
February 19, 2018

# re: Accessing Configuration in .NET Core Test Projects

I decided to just use my "Testing" environment but with real host. This way I can access any injected service (including configuration related ones) via GetService(). Of course in this case the tests are integration ones rather than unit tests, but for unit tests it's anyway mocking should be used instead of accessing configuration files.

public class BaseTestServerDependent 
{
    public TestServer Server { get; }
    public HttpClient Client { get; }

    public TestServerDependent()
    {
        ServiceCollectionExtensions.UseStaticRegistration = false;
        var hostBuilder = new WebHostBuilder()
            .UseEnvironment("Testing")
            .UseStartup<Startup>();

        Server = new TestServer(hostBuilder);
        Client = Server.CreateClient();
    }

    public TService GetService<TService>()
        where TService : class
    {
        return Server?.Host?.Services?.GetService(typeof(TService)) as TService;
    }
}

As an example of the test class:

public class MyTests : TestServerDependent
{
    [Test]
    public void Should_Access_Injected_Services()
    {
        var mySettings = GetService<IOptions<MySettings>>();
        Assert.NotNull(mySettings);
        Assert.NotNull(mySettings.Value);

        var myService = GetService<IMyService>();
        Assert.NotNull(service);

        // ... the rest of the test ...
    }
}

Hope that helps as well.


Rick Strahl
February 21, 2018

# re: Accessing Configuration in .NET Core Test Projects

@Jav - I'm not unit testing expert, but do you want to expand on that? How could you possibly test a component that has nested dependencies? At some point you'll have to test higher level components that have dependencies and that point you will most likely need DI to make that happen. Doing low level component unit tests w/o dependencies (or manually created dependencies) only get you so far in even a medium complex solution?


Andrew Lock
February 23, 2018

# re: Accessing Configuration in .NET Core Test Projects

Great post as always Rick. One thing, rather than copying the settings file from your web project to your test file, I'd suggest linking it. That way if your make changes to the settings file, your test project is updated at the same time. https://andrewlock.net/sharing-appsettings-json-configuration-files-between-projects-in-asp-net-core/


Rick Strahl
March 01, 2018

# re: Accessing Configuration in .NET Core Test Projects

@andrew - nice - didn't realize you could do that although I have to say I don't really see that use case. I can use UserSecrets for that scenario just as easily since multiple projects can share the same UserSecrets store on a local machine.


Thanh Doan
March 28, 2018

# re: Accessing Configuration in .NET Core Test Projects

Hi Rick,

Thank you for your great post. I followed your post and now I am able to run some tests for my configuration. I am using XUnit.

However, I am trying to test the way I managed secret/sensitive data configuration. Basically, I will have a default appsettings.json which has sensitive data configuration is empty of null like yours. I want to override those data configuration by Environment Variables. Therefore, I am using IFixture in XUnit to read an json file in order to set up the Environment variables. If I put tests into two different projects such as Dev Test in Dev project(not using IFixture) and Stagging Test in Stagging project using IFixture then they are all green. However, I put it into same project then some failed.

Any ideas for that issue?

Thanks


mmahouac
August 20, 2018

# re: Accessing Configuration in .NET Core Test Projects

Hi, Thank you for your post very usefull. I found also an easy way to get the configuration file

https://www.jerriepelser.com/blog/using-configuration-files-in-dotnet-core-unit-tests/


Justin James
August 21, 2018

# re: Accessing Configuration in .NET Core Test Projects

For .NET Core 2.1 in order for the TestHelper.GetIConfigurationRoot code to work I had to add some nuget packages. Looks like they split the different configuration options into different nuget packages. Below is the different packages that I had to add.

SetBasePath and AddJsonFile => Microsoft.Extensions.Configuration.Json

AddUserSecrets => Microsoft.Extensions.Configuration.UserSecrets

AddEnvironmentVariables => Microsoft.Extensions.Configuration.EnvironmentVariables

Susheel
September 28, 2018

# re: Accessing Configuration in .NET Core Test Projects

@Kevin,

Following additional packages are required.

Setting the base path of the app with SetBasePath. SetBasePath is made available to an app by referencing the Microsoft.Extensions.Configuration.FileExtensions package. Resolving sections of configuration files with GetSection. GetSection is made available to an app by referencing the Microsoft.Extensions.Configuration package. Binding configuration to .NET classes with Bind and Get. Bind and Get are made available to an app by referencing the Microsoft.Extensions.Configuration.Binder package. Get is available in ASP.NET Core 1.1 or later.


Alex
October 18, 2018

# re: Accessing Configuration in .NET Core Test Projects

Very useful post. Thanks Also for missing packages, the search box in MSDN is (finally) pretty handy, so queries by extension methods take you to the missing DLL.

https://docs.microsoft.com/en-gb/dotnet/api/index?view=aspnetcore-2.1

 

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2018