unit testing
app.config
application configuration
software development
C#

Can a unit test project load the target application's app.config file?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Yes, a unit test project can load the target application's configuration file, but not automatically in the way many developers expect. By default, the test runner uses the configuration file for the test assembly, not the one from the application under test. If your code relies on settings from app.config, you need to either copy those settings into the test project or load the target config explicitly.

What Configuration File a Test Runner Uses

In classic .NET Framework projects, the active configuration file is tied to the executable that is running. During a test run, that executable is usually the test host, and the configuration that matters is the test assembly config, often named something like MyTests.dll.config.

That means this assumption is usually wrong:

  • "My production project's app.config will automatically be visible from my test project."

What actually happens is:

  • the test process starts
  • 'ConfigurationManager reads the config associated with that process'
  • your target application's app.config is ignored unless you copied or mapped it

So if your unit test calls ConfigurationManager.AppSettings["ApiUrl"], it will read the test project's settings unless you deliberately redirect it.

The Simple Approach: Put Test Settings in the Test Project

For most unit tests, the best solution is to create an App.config inside the test project and place the needed settings there. This keeps tests isolated and avoids coupling them to the production deployment configuration.

Example test project config:

xml
1<?xml version="1.0" encoding="utf-8" ?>
2<configuration>
3  <appSettings>
4    <add key="ApiUrl" value="https://test.local" />
5    <add key="FeatureFlag" value="true" />
6  </appSettings>
7</configuration>

And the corresponding test code:

csharp
1using System.Configuration;
2using Xunit;
3
4public class ConfigTests
5{
6    [Fact]
7    public void Reads_Value_From_Test_Config()
8    {
9        string apiUrl = ConfigurationManager.AppSettings["ApiUrl"];
10        Assert.Equal("https://test.local", apiUrl);
11    }
12}

This approach is straightforward, deterministic, and usually what you want for unit tests.

Loading the Target Application Config Explicitly

If you truly need to inspect the target application's real config file, you can load it manually with ExeConfigurationFileMap. That lets you point ConfigurationManager at a specific file path instead of relying on the current process config.

csharp
1using System.Configuration;
2using Xunit;
3
4public class ExternalConfigTests
5{
6    [Fact]
7    public void Can_Load_Target_Application_Config()
8    {
9        var map = new ExeConfigurationFileMap
10        {
11            ExeConfigFilename = @"C:\Projects\MyApp\bin\Debug\MyApp.exe.config"
12        };
13
14        Configuration config = ConfigurationManager.OpenMappedExeConfiguration(
15            map,
16            ConfigurationUserLevel.None
17        );
18
19        string apiUrl = config.AppSettings.Settings["ApiUrl"].Value;
20        Assert.False(string.IsNullOrWhiteSpace(apiUrl));
21    }
22}

This is useful for integration-style tests or migration checks, but it comes with tradeoffs. The test now depends on a real file path and on the build output layout of another project.

A Better Design: Do Not Make Unit Tests Depend on app.config

If your code is hard to test without loading the production config file, that is often a design smell. Business logic usually becomes easier to test when configuration is injected instead of fetched statically.

For example, instead of this:

csharp
1using System.Configuration;
2
3public class ApiClient
4{
5    public string GetBaseUrl()
6    {
7        return ConfigurationManager.AppSettings["ApiUrl"];
8    }
9}

Prefer this:

csharp
1public class AppSettings
2{
3    public string ApiUrl { get; set; } = "";
4}
5
6public class ApiClient
7{
8    private readonly AppSettings settings;
9
10    public ApiClient(AppSettings settings)
11    {
12        this.settings = settings;
13    }
14
15    public string GetBaseUrl()
16    {
17        return settings.ApiUrl;
18    }
19}

Now a test can pass whatever settings it wants without relying on configuration files at all:

csharp
1using Xunit;
2
3public class ApiClientTests
4{
5    [Fact]
6    public void Uses_Injected_Settings()
7    {
8        var client = new ApiClient(new AppSettings { ApiUrl = "https://test.local" });
9        Assert.Equal("https://test.local", client.GetBaseUrl());
10    }
11}

That is a cleaner unit-testing model than trying to share a production app.config file.

Common Pitfalls

The biggest pitfall is assuming the application project's app.config is automatically copied into the test output. It usually is not.

Another pitfall is writing fragile tests that hard-code a bin/Debug path to another project. Those tests often break when the build configuration changes or the solution is moved.

Developers also confuse unit tests with integration tests. If the test needs the real deployed configuration, it is no longer a pure unit test and should be treated accordingly.

Finally, static access through ConfigurationManager makes code harder to test than constructor injection or options objects. If you control the code, refactoring the dependency is usually the better long-term fix.

Summary

  • A test project normally uses its own config file, not the target application's app.config.
  • You can load another config file explicitly with OpenMappedExeConfiguration.
  • For ordinary unit tests, adding settings to the test project is simpler and more reliable.
  • If tests need the production config to run, you may really be writing an integration test.
  • The cleanest design is to inject configuration into the code under test instead of reading it statically.

Course illustration
Course illustration

All Rights Reserved.