Can't read app.config in C# .NET Core unit test project with ConfigurationManager

C#Unit Testing.Net CoreMstestApp Config

C# Problem Overview


I've created a simple unit test project to read an app.config file. Target framework is Core 2.0. I also created a Core 2.0 console app, to sanity-check myself to make sure I wasn't doing anything weird (same test passed as expected in a .NET 4.6.1 unit test project).

The console app reads the app.config fine, but the unit test method fails and I cannot figure out why. Both are using a copy of the same app.config (not added as a link) and both have the System.Configuration.ConfigurationManager v4.4.1 NuGet package installed.

The App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="Test1" value ="This is test 1."/>
    <add key="Test2" value ="42"/>
    <add key="Test3" value ="-42"/>
    <add key="Test4" value="true"/>
    <add key="Test5" value="false"/>
    <add key="Test6" value ="101.101"/>
    <add key="Test7" value ="-1.2345"/>
  </appSettings>
</configuration>

The Unit Test

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Configuration;

namespace ConfigTest
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod()]
        public void ConfigTest()
        {
            foreach (string s in ConfigurationManager.AppSettings.AllKeys)
            {
                System.Console.WriteLine(s);
                System.Diagnostics.Debug.WriteLine(s);
            }

            //AllKeys.Length is 0? Should be 7...
            Assert.IsTrue(ConfigurationManager.AppSettings.AllKeys.Length == 7);
        }
    }
}

The Console App

using System;
using System.Configuration;

namespace ConfigTestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (string s in ConfigurationManager.AppSettings.AllKeys)
            {
                Console.WriteLine(s);
                System.Diagnostics.Debug.WriteLine(s);
            }

            //Outputs 7 as expected
            Console.WriteLine(ConfigurationManager.AppSettings.AllKeys.Length);
        }
    }
}  

Given that I'm still pretty new to the whole .NET Core world, am I doing something totally incorrect here? I sort of just feel crazy at the moment...

Both projects with an app.config

C# Solutions


Solution 1 - C#

Looking through the github issue's comments, I found a work around that can go in the msbuild file...

<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
  <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.dll.config" />
</Target>

This makes it easier to verify existing tests under .NET Core before porting the configuration data over to json configuration files.

Edit

If running under Resharper, the previous answer doesn't work as Resharper proxies the assembly, so you need

<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
  <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\ReSharperTestRunner64.dll.config" />
</Target>

Solution 2 - C#

If you check the result of the call to ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

It should tell you where the required configuration file should be while running unit tests for that assembly.

I found that instead of having an app.config file, ConfigurationManager was looking for a testhost.dll.config file.

This was for a project targeting netcoreapp2.1, with a reference to Microsoft.NET.Test.Sdk,NUnit 3.11 and Nunit3TestAdapter 3.12.0

Solution 3 - C#

.CORE 3.1 To find out what dll.config file was being used, I debugged the test by adding this line and looking to see what the value is.

string path = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;

Then I found out resharper was using testhost.dll.config and VStest was using testhost.x86.dll.config. I needed to add the following lines to the project file.

  <Target Name="CopyCustomContent" AfterTargets="AfterBuild">
    <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.dll.config" />
    <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.x86.dll.config" />
  </Target>

Solution 4 - C#

I came across the same issue with my xunit tests and solved it by using the instance of Configuration from ConfigurationManager. I put the static (normal) way it works in core, framework (but not unit tests) before I show the alternative way it works in all three:

		var appSettingValFromStatic = ConfigurationManager.AppSettings["mySetting"];
		var appSettingValFromInstance = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings.Settings["mySetting"].Value;

And here is a similar/related issue. In case anyone needs to get a section you can do a similar thing, though the type must change in the app config:

<configSections>
	<section name="customAppSettingsSection" type="System.Configuration.AppSettingsSection"/>
	<section name="customNameValueSectionHandlerSection" type="System.Configuration.NameValueSectionHandler"/>
</configSections>

<customAppSettingsSection>
	<add key="customKey" value="customValue" />
</customAppSettingsSection>

<customNameValueSectionHandlerSection>
	<add key="customKey" value="customValue" />
</customNameValueSectionHandlerSection>

Code to grab section:

		var valFromStatic = ((NameValueCollection)ConfigurationManager.GetSection("customNameValueSectionHandlerSection"))["customKey"];
		var valFromInstance = ((AppSettingsSection)ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).GetSection("customAppSettingsSection")).Settings["customKey"].Value;

I feel like I am also crazy, and I know there are newer ways of doing config in core, but if one wants to do something cross-platform this is the only way I know how. I'd be very interested if anyone has alternatives

Solution 5 - C#

For my mixed .NET-Core & .NET-Framework project, I added the following to the unit test global setup:

#if NETCOREAPP
using System.Configuration;
using System.IO;
using System.Reflection;
#endif

...

// In your global setup:
#if NETCOREAPP
    string configFile = $"{Assembly.GetExecutingAssembly().Location}.config";
    string outputConfigFile = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;
    File.Copy(configFile, outputConfigFile, true);
#endif

This copies the config file to the output path testhost.dll.config but should be resilient enough to account for future changes in the testing framework.

Or you can copy to below, which amounts to the same thing:

string outputConfigFile = Path.Combine(Path.GetDirectoryName(configFile), $"{Path.GetFileName(Assembly.GetEntryAssembly().Location)}.config");

Credit to @stop-cran and @PaulHatcher's solutions, this is a combination of those two.

Solution 6 - C#

None of the answers given here provide a viable workaround when you're dealing with code accessing directly the static ConfigurationManager properties such as AppSettings or ConnectionStrings.

The truth is, it is not possible at the moment. You can read through the discussion here to understand why: https://github.com/dotnet/corefx/issues/22101

There is talk to implement the support for it here: https://github.com/Microsoft/vstest/issues/1758

In my opinion it makes sense to support this scenario since it's been working on the .NET Framework plus System.Configuration.ConfigurationManager is a .NET Standard 2.0 library now.

Solution 7 - C#

A hacky, but working way is to copy the config to the same folder as an entry assembly, whatever it is:

[SetUpFixture]
public class ConfigKludge
{
    [OneTimeSetUp]
    public void Setup() =>
        File.Copy(
            Assembly.GetExecutingAssembly().Location + ".config",
            Assembly.GetEntryAssembly().Location + ".config",
            true);

    [OneTimeTearDown]
    public void Teardown() =>
        File.Delete(Assembly.GetEntryAssembly().Location + ".config");
}

Apart from adding this class, the only thing to make it work is to include app.config file in test project (without any copying options). It should be copied to the output folder as <your test project name>.dll.config at the build step, because it's kind of default logic.

Note the documentation for OneTimeSetUpAttribute: > Summary: > Identifies a method that is called once to perform setup before any child tests are run.

Although it should work for parallel test runs for a single project, there could be obvious troubles when running two test projects simultaneously, since the config would get overwritten.

However, it is still suitable for containerized test runs, like in Travis.

Solution 8 - C#

When we answer such well-researched and well-articulated question, we should better assume that it is being asked by an informed and intelligent being. And instead of patronizing them with the obvious about new, great ways of writing tonnes of boilerplate code to parse all sort of JSON et al, being forced on us and shoved down our throat by know-betters, we should focus on answering to the point.

Since the OP is already using System.Configuration to access settings, they already know how to arrive at this point. The only thing that is missing is one little touch: adding this line to the post-build event:

copy $(OutDir)<appname>.dll.config $(OutDir)testhost.dll.config

where <appname> is the project being unit-tested.

I applaud everyone who is still using (originally lame but workable) implementation of app.config because doing so protects our and our clients' investment in technology instead of reinventing the wheel. Amen.

Solution 9 - C#

The ConfigurationManager API will only use the configuration of the app that is currently running. In a unit test project, this means the app.config of the test project, not the console application.

.NET Core Applications aren't supposed to use app.config or ConfigurationManager, as it is a legacy "full framework" configuration system.

Consider using Microsoft.Extensions.Configuration instead to read JSON, XML or INI configuration files. See this doc: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration

Solution 10 - C#

Mercifully there is now a way to set the name of the expected configuration file at runtime. You can set the APP_CONFIG_FILE data for the current app domain.

I created the following SetUpFixture to do this automatically:

[SetUpFixture]
public class SetUpFixture
{
    [OneTimeSetUp]
    public void OneTimeSetUp()
    {
        var testDllName = Assembly.GetAssembly(GetType())
                                  .GetName()
                                  .Name;
        var configName = testDllName + ".dll.config";
        AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", configName);
    }
}

The relevant GitHub discussions are:

Solution 11 - C#

Usually in .NET Framework projects, any App.config file was copied to the bin folder by Visual Studio, with the name of your executable (myApp.exe.config) so it could be reachable in runtime. Not anymore in .NET Standard or Core Framework. You must manually copy and set the file in the bin/debug or release folder. After that it could be get with something like:

                string AssemblyName = System.IO.Path.GetFileName(System.Reflection.Assembly.GetEntryAssembly().GetName().CodeBase);
            AppConfig = (System.Configuration.Configuration)System.Configuration.ConfigurationManager.OpenExeConfiguration(AssemblyName);

Solution 12 - C#

While app.config exists in the root project folder add below string to Post-build event command line

xcopy /Y $(ProjectDir)app.config $(ProjectDir)$(OutDir)testhost.dll.config*

Solution 13 - C#

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBroots WaymbView Question on Stackoverflow
Solution 1 - C#Paul HatcherView Answer on Stackoverflow
Solution 2 - C#3DPrintScannerView Answer on Stackoverflow
Solution 3 - C#littleraniView Answer on Stackoverflow
Solution 4 - C#MarioView Answer on Stackoverflow
Solution 5 - C#DanielView Answer on Stackoverflow
Solution 6 - C#MoonStomView Answer on Stackoverflow
Solution 7 - C#stop-cranView Answer on Stackoverflow
Solution 8 - C#Jehedi ZafoomView Answer on Stackoverflow
Solution 9 - C#Martin UllrichView Answer on Stackoverflow
Solution 10 - C#aboy021View Answer on Stackoverflow
Solution 11 - C#Fidel OrozcoView Answer on Stackoverflow
Solution 12 - C#KH from UAView Answer on Stackoverflow
Solution 13 - C#Amir TouitouView Answer on Stackoverflow