I’m sure you’ve seen this pattern of using static fields to maintain application state:
public class AppState {
static MovieRepository movieRepository;
static StarRepository starRepository;
static {
AppState.movieRepository = new MovieRepository();
AppState.starRepository = new StarRepository();
}
}
One reason this is a bad idea is it can cause inconsistent test results. If one test modifies the application state and then a second test runs, the second test will use the first test’s state.
A simple example
I’ve created a worked example in my GitHub repo demonstrating the issue in JUnit 6 and JUnit 4. We have an AppState class containing static fields. The AppState is used by the main MovieDb class:
public void showMovies() {
MovieRepository movieRepository = AppState.getMovieRepository();
movieRepository.getMovies().forEach(out::println);
}
I’ve created tests for both classes:
@ExtendWith(MockitoExtension.class)
class MovieDbTest {
private static final Movie MOVIE_1 = new Movie("Back to the Future");
private static final Movie MOVIE_2 = new Movie("Jaws");
@Mock private MovieRepository movieRepository;
@Mock private PrintStream out;
@Captor private ArgumentCaptor<Movie> movieCaptor;
@Captor private ArgumentCaptor<String> outCaptor;
@InjectMocks private MovieDb db;
@BeforeEach
void setUp() {
when(movieRepository.getMovies()).thenReturn(Set.of(MOVIE_1, MOVIE_2));
AppState.movieRepository = movieRepository;
}
@Test
void showMovies_loadsMoviesFromRepository() {
db.showMovies();
verify(out).println(MOVIE_1);
verify(out).println(MOVIE_2);
}
}
class AppStateTest {
@Test
void initAppState_inititializesRepositories() {
MovieRepository movieRepository = AppState.getMovieRepository();
StarRepository starRepository = AppState.getStarRepository();
// Repositories are initialized
assertNotNull(movieRepository);
assertNotNull(starRepository);
// Initial repository state is empty
assertTrue(movieRepository.getMovies().isEmpty());
assertTrue(starRepository.getStars().isEmpty());
}
}
If I run the tests one at a time from IntelliJ IDEA, they both pass. If I run both tests in a single execution, we get intermittent failure:


The tests pass only if AppStateTest runs first.
What happened?
MovieDbTest configures the AppState to use mocks. The mocks are set up for the MovieDbTest cases only. However, when the AppStateTest starts, the AppState is ‘dirty’. Static fields (also known as class variables) are maintained for the lifetime of the class. This is usually the lifetime of the execution. JUnit runs all test cases in a single JVM. So changes to static fields in one test will persist to other tests in the same run.
This is particularly nasty as the tests will now depend on the order that JUnit chooses to run them. This causes intermittently failing test results. If you’re maintaining hundreds of tests, it can be extremely difficult to track down the cause of your test failure. The root cause is often in a different test class.
Fixing the test
First, consider this a code smell. Your test is telling you that your application is brittle and that changes made in one place can ripple out to other parts of the code. This perhaps indicates that classes are tightly coupled and could cause maintenance problems or even bugs later on. However, if you want to fix the test before fixing the code, you can clean up their side effects when they complete.
JUnit 5/6 provides the @BeforeEach and @AfterEach annotations to setup / teardown for each test case (JUnit 4 had @Before and @After). When you define @BeforeEach consider also defining @AfterEach to destroy / reset any state you’ve set up. Often you don’t need to. When you’re working with object instances (not classes), they’re scoped to the lifetime of the test method anyway so are implicitly destroyed at the end of a test case. However, if you modify long-lived state, including static fields, always clean up afterwards.
MovieRepository originalRepository;
@BeforeEach
public void setUp() {
when(movieRepository.getMovies()).thenReturn(Set.of(MOVIE_1, MOVIE_2));
originalRepository = AppState.movieRepository;
AppState.movieRepository = movieRepository;
}
@AfterEach
public void tearDown() throws Exception {
AppState.movieRepository = originalRepository;
}
If appropriate, this set / reset behaviour can be defined in a JUnit 5/6 extension (@ExtendWith) implementing BeforeEachCallback and AfterEachCallback or extending ExternalResourceSupport (deprecated in JUnit 6). The same can be achieved in JUnit 4 with a @Rule by implementing TestRule or extending ExternalResource.
How to manage application state
The root cause of this test failure is brittle application design. Static fields have legitimate uses but are easily abused and can cause problems with tests and correctness. Often the issues are subtle and hard to reproduce. They can cause intermittent test failures and race conditions in application code. It’s not always wrong to use static fields. However, I’ve seen enough issues with them that I consider them a red flag. When I encounter the static keyword I always give it a little more attention than the rest of the code.
The reason for using static in this example is to reduce the need to pass the specific MovieRepository / StarRepository instances from one class to another. This simplifies interfaces (good!). However, a similar result can be achieved by using an IoC container (such as Spring) to manage your application instances for you.
Be First to Comment