all Technical posts

Building Blocks for Test Configuration in Integration-Like Test Suites

When you're testing software that connects to real external resources, like servers or databases, it's important to have separate test environments set up. This is similar to how you need to configure your test application settings. These test environments help ensure that your tests are accurate and reliable. Read on for more.

Application configuration in testing

Any integration-like test suite needs a set of values to authenticate and interact with any type of remote resource. Usually, the system you are testing is either running as self-contained (when a test starts the service) or running remotely (service deployment is running outside the test run). Either way, the service will need its configuration values to begin correctly. This could be whether by storing data in a backend datastore, authenticating an incoming request from the test, or any other integration-like test scenario.

Because of this, the tests will have to handle an application configuration throughout the test run. The fun begins when you realize that this is no different from the application configuration used in the implementation code.

Test configuration model

The .NET-way of handling application configuration is by using an instance of the IConfiguration interface, usually constructed via the ConfigurationBuilder type. This is no different from the application code, where you need to set up from which source you want to retrieve your configuration values.

I’m a big fan of making sure that your tests run everywhere. That means on any remote build server and on any local development machine. That is also the reason why each of the following examples has a appsettings.local.json file. This file should be ignored by your source control system so you are free to include any necessary private information. The regular appsettings.json file in this example will be the one that contains tokens (like #{Project.KeyVault.VaultName}#) which you can replace during the build run on the remote server.

This is great and already a big help. Only when re-using this kind of setup for multiple test suites can this lead to tedious duplication. That is why I usually abstract this in some kind of test implementation of the IConfiguration. That way, all test-related configuration is placed together.

Scalable test configuration sections

As the TestConfig is just another IConfiguration, you can access your configuration values just like you can in your application code. However, when the amount of configuration values grow, you may want to move to more scalable variants. We can create separate models for grouped configuration sections.

This is different from just ‘binding’ it to your section. When using these separate test configuration models, you make sure that a validation check exists before you use the values. It may be that there is a configuration value missing and this way of working will make sure that you can quickly spot this instead of debugging or waiting for the entire test to fail.

Test configuration environments

Applications may be deployed on different environments, and the tests should be aware of what remote services they should use during the test run. This is no different from a regular application. ASP.NET Core has a default way of selecting the right kind of appsettings.json file based on a DOTNET_ENVIRONMENT variable. This is the default behavior, but behind the scenes it is nothing more than a simple string concatenation of an environment value.

This example shows how a DEV and QA environment gets selected.

👀 Notice that the remaining test code will never be aware of which environment it runs. This is all abstracted away in the TestConfig.

There is a clear reason why there’s both the option to pass in the test environment and the option to use an environment variable. A focused reader will already know: the tests could be run both on the remote server and on local development machines. Remotely, you set the environment variable via a build parameter; locally, you pass in the preferred environment as an argument.

Conclusion

Test code seems like a place where there is less time, effort and budget. We should not forget that there is already a lot that is available to make the test development workspace more fun to work with. Often, I find it a much more relaxing environment to work in, as you do not have to worry about breaking changes or other public-related issues. You can be as free and creative to write and come up with your own test scenario, much freer than in any implementation code. You can mercilessly refactor the test code until it is the cleanest and most scalable place to work in.

This is a great power; I hope you use it wisely.

Thanks for reading!
Stijn

Subscribe to our RSS feed

Thanks, we've sent the link to your inbox

Invalid email address

Submit

Your download should start shortly!

Stay in Touch - Subscribe to Our Newsletter

Keep up to date with industry trends, events and the latest customer stories

Invalid email address

Submit

Great you’re on the list!