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:
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
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:
What should I do to force Junit share an instance of TokenManager class between different test classes?
Is there a better solution to this problem?
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.