javajunit5

Sharing instance of a class between multiple tests running in parallel in Junit5


I'm working on a test framework for a project that consists of multiple APIs. For each of them I have a different client that needs to authorize with different token. Tests are run in parallel.

My solution was creating a class that holds tokens in a map, mapping client to his/her tokens for different services.

public class TokenManager {
    private static final Map<Client, Map<String, Token>> tokens = new EnumMap<>(Client.class);
    
    public static String getToken(Client client, String service) {
        if (isTokenExpired(client, service)) {
            renewToken(client, service);
        }
        return tokens.get(client).get(service).getAccessToken();
}

...and call the getToken() in a @BeforeEach method of a base class that each test then extends, like so

class BaseTest {
    
    @BeforeEach
    void setUp() {
        TokenManager.getToken(clientFoo, serviceBar);
    }
}
class SomeTestClass extends BaseTest {
    
   @Override
   void setUp() {
        super.setUp();
        // test-specific setup
    }

    @Test
    void someTest() {
        assertTrue(true);
    }

    @Test
    void otherTest() {
        assertTrue(false);
    }

My assumption was that an instance of TokenManager class was created once, before a run of all tests (either through maven, or Intellij runner). In reality, it's created for each test class. So tokens are shared between different tests of the same class, but not between tests of different classes. As mentioned before, tests are run in parallel.

My problems are:

  1. Each test class creates new tokens, when I'd like the instance of TokenManager to be created once and supply all tests with proper tokens, that are only refreshed when expired

  2. Once in a while I get NullPointerException stating:

    java.lang.NullPointerException: Cannot invoke "Token.getAccessToken()" because the return value of "java.util.Map.get(Object)" is null
     at TokenManager.getToken(TokenManager.java:57)
     at BaseTest.setUp(BaseTest.java:9)
     at SomeTest.setUp(SomeTest.java:37)
    

My questions are:

  1. What should I do to force Junit share an instance of TokenManager class between different test classes?

  2. Is there a better solution to this problem?


Solution

  • My issue was resolved simply by adding @Synchronized to getToken method. So, as @rzwitserloot pointed out, it was a problem of concurrency, and only of concurrency. Instance of TokenManager is indeed created once, before all tests (not @BeforeAll, but rather "before any") and is shared between all test classes.