3 Things to Ask Yourself Before Using JUnit 4 @AfterClass

The @AfterClass annotation in JUnit4 (or the @AfterAll annotation in JUnit5) indicate that the method annotated be executed after all the tests in that class are complete.

Here are a few things to think about before you choose to use this feature in your tests.

Will shared state between tests cause unintended outcomes?

When running tests in parallel (using Surefire, for example), state will be shared in any object managed by @BeforeClass and @AfterClass.

For example, if you want to use a WebDriver instance in all your tests, make sure you the state that exists works for each of your tests each time they are run. Not realizing where your shared state is and what is shared can lead to flaky tests.

Can I use @After instead?

Many times the use of @AfterClass is not necessary and (as discussed above) can lead to unpredictable results. Using @After will handle that state for each test, not in the parent class context.

Example using @AfterClass:

private static Database database;

@BeforeClass
public static void before() {
    database = new Database();
}

@Test
public void test() {
    database.add("test");
    // test some functionality
}

@AfterClass
public static void after() {
    // do some clean up of the Database object
    database = null;
}

Perhaps you can't use @After with this particular Database object because you need to talk to a real database on your local machine. But, can you use a drop-in replacement that will work with each instance of a test?

Example using @After:

private InMemoryDatabase database;

@Before
public void before() {
    database = new InMemoryDatabase();
}

@Test
public void test() {
    database.add("test");
    // test some functionality
}

@After
public void after() {
    // do some clean up of the database object
    database = null;
}

Each instance of this InMemoryDatabase is unique to the test being run, only in used in that scope. This is safer and there is no shared state between tests.

Do I need additional resources at all?

Can you replace the notion of resource instantiation and finalization by satisfying a simple interface?

When you use dependency injection, you'll need to create the object you're testing with the objects you are injecting, right? Yes, and no. Instead of injecting implementations, inject just the interfaces. That gives you more flexibility in satisfying those dependencies.

Let me explain using the fictional Database I used above. Instead of the Database object, you could use the DatabaseInterface, which the Database object implements:

public interface DatabaseAddInterface {
    void add(final Object object);
}

public class Database implements DatabaseAddInterface {
    public void add(final Object object) {
        // implementation
    }
}

To satisfy the interface, you don't need a concrete implementation. This can be accomplished using a lambda (in Java 8+), which makes your test simpler:

@Test
public void test() {
    final MyClass myClass = new MyClass(object -> { });
    // test MyClass 
}

In this example, you just need something that satisfies the interface, but you might need to add some functionality to this implementation. The great thing about it is, you can decide what you need each time you need to satisfy it.

There's an additional advantage to using this method. It forces you to follow the Interface Segregation Principle. It also nudges you to use a functional mindset instead of a procedural one. Writing logic as functions can make your code easier to read and make it more reusable.

🐾