ConfigurationManager.AppSettings Returns Null In Unit Test Project
Asked Answered
A

4

26

I have a C# unit test project with application settings in the app.config file. I am testing a class that exists in a different project. That class depends on both, ConfigurationManager.AppSettings and ConfigurationManager.ConnectionStrings.

The project that the class being tested resides in does not have an app.config file. I would have thought that because the class is being instantiated in the context of the unit test project that it would use the unit test project's app.config file. Indeed, that does seem to be the case for the connection string.

The class retrieves the connection string without any issues. However, when the class tries to retrieve any application settings the configuration manager always returns null. What is going on here?

Edit 1

I thought maybe it would be a good idea to try load some settings in the test project to see what happens. I tried to load the setting in the unit test immediately before calling the code that instantiates the class in the external project. Same result, nothing. I guess I can exclude the other project from the equation for the time being.

Here is an excerpt from my config file:

<configSections>
  <sectionGroup name="applicationSettings"
                type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
    <section name="MyNamespace.Properties.Settings"
             type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
             requirePermission="false" />
  </sectionGroup>
</configSections>

...

<applicationSettings>
  <MyNamespace.Properties.Settings>
    <setting name="Bing_Key"
             serializeAs="String">
      <value>...</value>
    </setting>
  </MyNamespace.Properties.Settings>
</applicationSettings>

and here is how I am attempting to load the setting:

string test = System.Configuration.ConfigurationManager.AppSettings["Bing_Key"];
Adamek answered 19/6, 2014 at 13:21 Comment(11)
Did you check that the build action on the App.config file needs to be Content and the Copy to Output Directory setting needs to be "Copy if newer."Espinosa
Neither of those things were set as you described. Changing them did not solve the problem.Adamek
In your app.config, do the config sections types and namespaces match CM.AppSettings namespace?Attribute
Have you copied the appSettings section and pasted in the unit test project's app.config?Crayon
Have you tried adding the configuration file for your regular project as a link in your test project? Does the test project have a reference to System.Configuration?Mantua
And in the stuff that user500615 suggested, did you do a full clean/rebuild after the changes?Mantua
I am not sure actually. Are you talking about the auto-generated settings cs file? The namespace in the auto-generated file is MyNamespace.Properties and the class definition is internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBaseAdamek
Try removing the <MyNamespace.Properties.Settings> and </MyNamespace.Properties.Settings> parts, might solve the problemWellbred
I get a run-time exception when I do that. I am not sure I could keep those out anyways. They are automatically generated by visual studio whenever I edit the settings tab in the project properties.Adamek
@Crayon The unit test project is the project with the app.config file. The project containing the class being tested does not have its own app.config file.Adamek
@Jennifer S See response to Tacoman667 regarding linking the app.config file. I added a link to System.Configuration in the test project when I tried to resolve the application setting (see Edit 1). When I implemented user500615's suggestion I deleted the obj and bin folders, cleaned and rebuilt the project.Adamek
C
11

You mentioned settings in the project properties. See if you can access the setting this way:

string test = Properties.Settings.Default.Bing_Key;

You may need to get the executing assembly of where the project settings file is defined, but try this first.

EDIT

When using Visual Studio's project settings file, it adds stuff to your app.config and creates the app.config if it is not present. ConfigurationManager CAN'T touch these settings! You can only get to these specific generated project.settings file from using the above static method. If you want to use ConfigurationManager, you will need to hand write your app.config. Add your settings to it like so:

<appSettings>
  <add key="bing_api" value="whatever"/>
</appSettings>
Creep answered 19/6, 2014 at 15:25 Comment(9)
That works from within the unit test project. However, there is no Properties.Settings class in the external project that is being tested.Adamek
Correct me if I'm wrong, but it sounds like you need that external project to be able to hook into your project settings, wherever they may happen to live. If that's the case, this might be helpful for accessing that settings file across different assemblies: #2548976Creep
Correct me if I am wrong (I probably am) but I thought the app.config file was kind of a like an global/ambient container loaded into the thread of whatever the main application was (the unit test project in this case). Then any other dlls (the system under test in this case), when reading any config properties, would read the properties from that same global config container.Adamek
@johnnymumble settings are based of type and namespace, so they would have to matchAttribute
This is correct, but IIRC project settings work differently than a straight up app.config. To go this route, ditch your project settings file (save your stuff first) and add a fresh app.config to your unit test (or main exe) project. You will then add your settings to <appSettings> <add key="bing_api" value="whatever"/> </appSettings>.Creep
Additionally, assuming that what you are saying is correct I do not understand two points. The first is that the external class has no problem loading the connection string from the unit test project's app.config file. It just falls down when it tries to read application settings. The second is that the unit test project itself is unable to read application settings using ConfigurationManger.AppSettings.Adamek
This is because you are going the route of project settings, which ConfigurationManager does not like. You see your project settings inside an app.config file, but it won't play ball with the method you are using. It looks the same as a handwritten app.config, but it isn't for whatever reason. If you want to use ConfigurationManager, you'll need to not use project settings and instead go with a hand written app.config.Creep
Oh my God Bill! You are a saint. The issue was that I was conflating the appSettings section used by ConfigurationManager.AppSettings and the applicationSettings section generated by Visual Studio. I thought they were one in the same. They are not. You original answer is not quite correct but it did lead me to the correct solution. If you edit it I will mark this as the answer.Adamek
Glad to be of help! I went through the same pain not so long ago. Answer has been edited :)Creep
C
18

If you're using .NET Core your problem could be a known issue caused by the fact that the test process runs as testhost.dll (or testhost.x86.dll), which means the runtime config file is expected to be named "testhost.dll.config" (or "testhost.x86.dll.config"), instead of the app.config output (ex: "MyLibrary.Tests.dll.config").

To fix it, add the code below to your project file (.csproj, etc) inside of root node <Project>. During build, two copies of app.config will be put in the output directory and named "testhost.dll.config" and "testhost.x86.dll.config", which will get your app settings working again. (You only need 1 of these files but it's safer to include both.)

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

I recommend app.config only as a temporary solution. If you're like me you might have run into the problem while upgrading a .NET Framework project to .NET Core and needed a quick fix. But don't forget to look into the new, more elegant solutions provided by .NET Core for storing app settings.

Covered answered 14/12, 2019 at 19:59 Comment(2)
What should we use with .NET Core for storing app settings for Desktop WPF App?Balaklava
@Balaklava I use appsettings.json files that get loaded using the options pattern. See here for an overview.Covered
T
14

Consider refactoring your code that accesses the config to use a wrapper. Then you can write mocks for the wrapper class and not have to deal with the importing of the configuration file for the test.

In a library that is common to both, have something like this:

public interface IConfigurationWrapper {

    string GetValue(string key);
    bool HasKey(string key);
}

Then, in your libraries that need to access config, inject an instance of this interface type into the class that needs to read config.

public class MyClassOne {
    
    private IConfigurationWrapper _configWrapper;

    public MyClassOne(IConfigurationWrapper wrapper) {
        _configWrapper = wrapper;
    } // end constructor

    public void MethodThatDependsOnConfiguration() {
        string configValue = "";
        if(_configWrapper.HasKey("MySetting")) {
            configValue = _configWrapper.GetValue("MySetting");
        }
    } // end method

} // end class MyClassOne

Then, in one of your libraries, create an implementation that depends on the config file.

public class AppConfigWrapper : IConfigurationWrapper {
    
    public string GetValue(string key) {
        return ConfigurationManager.AppSettings[key];
    }

    public bool HasKey(string key) {
       return ConfigurationManager.AppSettings.AllKeys.Select((string x) => x.ToUpperInvariant()).Contains(key.ToUpperInvariant());
    }
}

Then, in the code that calls your class.

//Some method container
MyClassOne dataClass = new MyClassOne(new AppConfigWrapper());

dataClass.MethodThatDependsOnConfiguration();

Then in your test, you are free from dependency bondage. :) You can either create a fake version that implements IConfigurationWrapper and pass it in for your test, where you hard-code the return values from the GetValue and HasKey functions, or if you're using a mocking library like Moq:

Mock<IConfigurationWrapper> fakeWrapper = new Mock<IConfigurationWrapper>();

fakeWrapper.Setup((x) => x.GetValue(It.IsAny<string>)).Returns("We just bypassed config.");

MyClassOne testObject = new MyClassOne(fakeWrapper.Object);
testObject.MethodThatDependsOnConfiguration();

Here is an article that covers the concept (albeit, for web forms, but the concepts are the same): http://www.schwammysays.net/how-to-unit-test-code-that-uses-appsettings-from-web-config/

Theretofore answered 19/6, 2014 at 15:50 Comment(0)
C
11

You mentioned settings in the project properties. See if you can access the setting this way:

string test = Properties.Settings.Default.Bing_Key;

You may need to get the executing assembly of where the project settings file is defined, but try this first.

EDIT

When using Visual Studio's project settings file, it adds stuff to your app.config and creates the app.config if it is not present. ConfigurationManager CAN'T touch these settings! You can only get to these specific generated project.settings file from using the above static method. If you want to use ConfigurationManager, you will need to hand write your app.config. Add your settings to it like so:

<appSettings>
  <add key="bing_api" value="whatever"/>
</appSettings>
Creep answered 19/6, 2014 at 15:25 Comment(9)
That works from within the unit test project. However, there is no Properties.Settings class in the external project that is being tested.Adamek
Correct me if I'm wrong, but it sounds like you need that external project to be able to hook into your project settings, wherever they may happen to live. If that's the case, this might be helpful for accessing that settings file across different assemblies: #2548976Creep
Correct me if I am wrong (I probably am) but I thought the app.config file was kind of a like an global/ambient container loaded into the thread of whatever the main application was (the unit test project in this case). Then any other dlls (the system under test in this case), when reading any config properties, would read the properties from that same global config container.Adamek
@johnnymumble settings are based of type and namespace, so they would have to matchAttribute
This is correct, but IIRC project settings work differently than a straight up app.config. To go this route, ditch your project settings file (save your stuff first) and add a fresh app.config to your unit test (or main exe) project. You will then add your settings to <appSettings> <add key="bing_api" value="whatever"/> </appSettings>.Creep
Additionally, assuming that what you are saying is correct I do not understand two points. The first is that the external class has no problem loading the connection string from the unit test project's app.config file. It just falls down when it tries to read application settings. The second is that the unit test project itself is unable to read application settings using ConfigurationManger.AppSettings.Adamek
This is because you are going the route of project settings, which ConfigurationManager does not like. You see your project settings inside an app.config file, but it won't play ball with the method you are using. It looks the same as a handwritten app.config, but it isn't for whatever reason. If you want to use ConfigurationManager, you'll need to not use project settings and instead go with a hand written app.config.Creep
Oh my God Bill! You are a saint. The issue was that I was conflating the appSettings section used by ConfigurationManager.AppSettings and the applicationSettings section generated by Visual Studio. I thought they were one in the same. They are not. You original answer is not quite correct but it did lead me to the correct solution. If you edit it I will mark this as the answer.Adamek
Glad to be of help! I went through the same pain not so long ago. Answer has been edited :)Creep
S
2

And then he screamed "NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO".

Cite: I have a C# unit test project with application settings in the app.config file. I am testing a class that exists in a different project. That class depends on both, ConfigurationManager.AppSettings and ConfigurationManager.ConnectionStrings.

You don't do this. EVER!!!! Why? because you have now created a dependency. Instead, use dependency injection so the class can do its work without having to peak into the configuration file that belongs to the application.

Suicidal answered 13/7, 2018 at 21:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.