It is an unfortunate reality that it is not feasible to isolate your code from all shared state. Although the use of repositories, wrappers and similar can be used to provide decoupling their user is not always feasible. It is also desirable to test the implementation of the code providing the decoupling which will force us to work with the shared state.

From a unit testing perspective shared state may be anything mutable that is visible between the execution of any two tests. This can include static properties, call context, relational databases and file contents. It may also include state created in the unit tests as part of the test fixture. When a test relies on this state having particular properties it becomes vulnerable to anything that changes that state. This is a recipe for disaster.

If a test assumes that some shared state has a particular value that it does not have then the result of the test is unpredictable. The test may pass, or it may fail. It will tend to do this fairly reliably for a particular value, but what this value is can change. The value may be altered by another test or by an external process, which means the success of a unit test is now determined by some external factor. This means that the test will randomly fail even if the unit it is testing has not changed in any way. This makes the test worse than useless as it will represent noise that can cover the introduction of real defects.

I have encountered this when I made the mistake of having unit tests that relied on a test database. This led to a situation where the order tests were run in would determine the success of the test run. Some tests would make changes to the database that would break other tests, and would need to be run after those tests. Other tests would add state to the database that yet other tests would depend on, requiring them to be run earlier. This quickly became very complex and fragile and it was not uncommon for some changes to break most of the tests in the system. This is a mistake I have resolved to never make again.

The solution for the above would have been to use mocking to prevent interaction with the database when testing the business units. This would have resolved the issues with most of the tests. But there are some elements that intrinsically need to deal with external state. It’s no use adding an extra layer of indirection in the data access code to prevent having to deal with the database, you still need to test the code that implements the new abstractions you have provided. This implies a need to deal with this state explicitly in your tests.

The first thing to do is identify exactly what shared state the unit under test interacts with. There are then two strategies for dealing with this state. Only one strategy should be applied any state item and it should be applied consistently across the system.

My preference is to assign a known good value to the state during the test setup. This ensures that the responsibility of establishing the test pre-requisites lies with the test itself and that the test will not fail as a result of a deficiency in some other test. This can be hugely beneficial in tracking the actual cause of a test failure as the failing test is highly likely to be related to the actual cause of a failure.

The alternative is to have each test clean up after itself. One strategy I have seen used with tests that interact with a database is for a transaction to be started at the start of a test and rolled back after its completion. This undoes any changes to the shared state the test may have performed. This type of approach may be significantly more efficient in some scenarios. The downside (which is significant) is that tests will now rely on the proper behaviour of other tests and the root cause of an error may be more difficult to detect. It may also lead to intermittent test failures dependent on such factors as the order in which tests are run. As tests are code and thus subject to defects I would not use this strategy unless it is infeasible to do otherwise.