ASP.NET Core provides great support for integration testing of Web APIs. You can host the server in the test process and still make requests over HTTP:
this.server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>();
this.Client = this.server.CreateClient();
var value = await this.Client.GetStringAsync("config/Key1");
However, if your app reads its configuration from the appsetting.json
file, you'll quickly find that the test server cannot find your regular configuration file.
Let's say I have the following controller to retrieve values from my configuration file:
[Route("[controller]")]
[ApiController]
public class ConfigController : ControllerBase
{
private readonly IConfiguration configuration;
public ConfigController(IConfiguration configuration)
{
this.configuration = configuration;
}
[HttpGet]
[Route("{key}")]
public string Get(string key)
{
return configuration[$"Settings:{key}"];
}
}
For this to work, I'd need to have a Settings
section in the appsetting.json
file of my Web API project:
{
"Settings": {
"Key1": "Value 1"
}
}
With the above configuration, the config/Key1
endpoint would return Value 1
. However, the following test would fail:
[Test]
public async Task GetConfig()
{
this.server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>();
this.Client = this.server.CreateClient();
var value = await this.Client.GetStringAsync("config/Key1");
Assert.That(value, Is.EqualTo("Value 1"));
}
When running inside the test project, the test server cannot find the configuration file of the Web API project. There are several ways to fix this:
- You could create a different configuration file in the test project.
- You could modify your build to copy the configuration file from the Web API project to the build output folder of the test project.
- You could configure the test server to find the configuration file in the Web API project.
I decided to go with the last option. It seemed the easiest to implement and has worked well enough for me so far.
I added custom configuration to my test server setup:
var configuration = new ConfigurationBuilder()
.AddJsonFile(
Path.Combine(settingsFilePath, "appsettings.json"),
optional: true,
reloadOnChange: true)
.AddJsonFile(
Path.Combine(settingsFilePath, "appsettings.Development.json"),
optional: true,
reloadOnChange: true)
.Build();
this.server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.UseConfiguration(configuration));
I hid the hardcoded path to the configuration files in the private settingsFilePath
field:
private readonly string settingsFilePath = Path.GetFullPath(
Path.Combine("..", "..", "..", "..", "WebApiTesting"));
With this updated configuration, the above test passed.
To avoid repeating the setup code in every test, I moved it to a test base class:
public abstract class WebApiTestBase
{
private readonly string settingsFilePath = Path.GetFullPath(
Path.Combine("..", "..", "..", "..", "WebApiTesting"));
private TestServer server;
protected HttpClient Client { get; private set; }
protected abstract string ApiRoute { get; }
[SetUp]
public void Setup()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile(
Path.Combine(settingsFilePath, "appsettings.json"),
optional: true,
reloadOnChange: true)
.AddJsonFile(
Path.Combine(settingsFilePath, "appsettings.Development.json"),
optional: true,
reloadOnChange: true)
.Build();
this.server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.UseConfiguration(configuration));
this.Client = this.server.CreateClient();
this.Client.BaseAddress = new Uri(
this.Client.BaseAddress.ToString() + this.ApiRoute);
}
}
The abstract ApiRoute
property is used to avoid repeating the controller base path in each test, since I can now easily define it at the test class level:
public class ConfigTests : WebApiTestBase
{
protected override string ApiRoute => "config/";
[Test]
public async Task GetConfig()
{
var value = await this.Client.GetStringAsync("Key1");
Assert.That(value, Is.EqualTo("Value 1"));
}
}
Individual tests need only contain the action method part of the path.
You can find working example code for this approach in my GitHub repository.
ASP.NET Core includes a test server that can be used for in-process Web API integration tests. By default, when the application needs a configuration file, it'll try to read it from the test project. In this post, I've described my approach to reading the application configuration file from the test project.