all Technical posts

Reusable Integration Testing Patterns: Enhancing Test Configuration, Test Fixtures, and Clean Slate Strategies

Integration testing speaks its own language. Understanding this language is key to writing reliable integration tests. This post gives a broad view of some of the 'words' in this language.

Test configuration model

Integration tests are by definition tests that verify code relationships, which usually involve external resources. We need configuration values and secrets to interact with these resources as we need to know where they are located and how (authentication/authorization) we can contact them. This is very similar to the regular project implementation, where you also need these values and secrets to interact with the same resources. Only in this case, the integration test will use them to set up/remove any values or verify the state during the test assertion.

Because this is so similar to implementation code, we can use the same structure to load them in our tests. Using Microsoft’s IConfiguration model and load appsettings.json files, we can load them with ease. The great thing about this approach is that it allows test environments, so that your test only includes the necessary types to run the test, but includes no information on where the test is run. In the same spirit, we can make sure that the values/secrets returned are different when the tests are run on a local development environment.

This is a great way of configuring your tests and is looked at on a deeper level in this dedicated post.

Temporary test fixture

Closely related to external resources are test fixtures. These are types that needs to be created to set up the necessary environment state for your test to run. This could be as simple as an environment variable and as complex as a filled Azure Table storage — anything that your test needs to run. These fixtures will evolve together with the requirements that your tests need. A test might require a faulty Table storage entry or an invalid environment variable, and your test fixture should be flexible enough to provide the test’s demands.

🚩 Passing null removes the environment variable.

Because an integration test never comes alone, we should be careful of how test fixtures are torn down. If a test fixture leaves something lingering behind after the integration test is run, it might conflict with the requirements of the next test. xUnit testing paradigm has a great word for this: Interacting Tests. That is why test fixtures are usually temporary fixtures, which means that they revert their change during a disposable action.

Test fixtures are usually one of the most reused components in a test suite: take care of them accordingly.

Clean slate & disposable collection

This last pattern is less ‘high-level’ than the two previous ones but is very closely related to disposable actions during integration tests. Once you start disposing of more than a single temporary fixture/state, you are at a point where one disposable action could ruin another action: if one fails to dispose of itself, are the other ones still trying to dispose?

This is a common question that can be easily solved with a ‘disposable collection’. This collection acts as a set of disposable resources and makes sure that each item is disposed of correctly – maybe even with a retry.
An example of such a collection:

👀 Note that we save the exceptions for later and throw them only at the very end, making sure that each disposable action first has the chance to dispose of itself.

In the same fashion, we can first clean up a certain resource before we start setting up our test fixture. This could be cleaning a disk folder or re-creating an Azure resource: anything that could provide some additional reassurance that we have a clean slate before the test fixture is set up and the test is run.

Conclusion

These patterns or ‘words’ in the integration testing language came from writing lots and lots of tests. This is how patterns usually originate: a repeated task or mindset that can be applied to any numerous scenarios in any project.

There are many more integration test patterns that make the development and testing workspace a great place to be. One of them is ‘controlled teardown’ where a tester is able to configure during test development which test fixture should be disposed and what should be left alone. This is a great way to improve defect localization.

Thanks for reading!
Stijn

Subscribe to our RSS feed

Hi there,
how can we help?

Got a project in mind?

Connect with us

Let's talk

Let's talk

Thanks, we'll be in touch soon!

Call us

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!