1. Overview
In this tutorial, we’ll explore how to effectively mock JWT (JSON Web Token) for unit testing Spring Security applications that use JWT authentication. Testing JWT-secured endpoints often requires simulating different JWT scenarios without relying on actual token generation or validation. This approach allows us to write robust unit tests without the complexity of managing real JWT tokens during testing.
Mocking JWT decoding is important in unit testing because it allows us to isolate the authentication logic from external dependencies, such as token generation services or third-party identity providers. By simulating different JWT scenarios, we can ensure that our application handles valid tokens, custom claims, invalid tokens, and expired tokens correctly.
We’ll learn how to use Mockito to mock the JwtDecoder, create custom JWT claims, and test various scenarios. By the end of this tutorial, we’ll be able to write comprehensive unit tests for Spring Security JWT-based authentication logic.
2. Setup and Configuration
Before we begin writing tests, let’s set up our testing environment with the necessary dependencies.
2.1. Dependencies
We’ll use Spring Security OAuth2, Mockito, and JUnit 5 for our tests:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>6.4.2</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.15.2</version>
<scope>test</scope>
</dependency>
The spring-security-oauth2-jose dependency supports JWT in Spring Security, including the JwtDecoder interface, which is used to decode and validate JWTs. The mockito-core dependency allows us to mock dependencies in our tests, ensuring that we can isolate the unit under test, UserController, from external systems.
2.2. Create UserController
Next, we’ll create UserController with the @GetMapping(“/user”) endpoint to retrieve user information based on a JWT token. It validates the token, checks for expiration, and extracts the user’s subject:
@GetMapping("/user")
public ResponseEntity<String> getUserInfo(@AuthenticationPrincipal Jwt jwt) {
if (jwt == null || jwt.getSubject() == null) {
throw new JwtValidationException("Invalid token", Arrays.asList(new OAuth2Error("invalid_token")));
}
Instant expiration = jwt.getExpiresAt();
if (expiration != null && expiration.isBefore(Instant.now())) {
throw new JwtValidationException("Token has expired", Arrays.asList(new OAuth2Error("expired_token")));
}
return ResponseEntity.ok("Hello, " + jwt.getSubject());
}
2.3. Setting up the Test Class
Let’s create a test class MockJwtDecoderJUnitTest and use Mockito to mock the JwtDecoder. Here’s the initial setup:
@ExtendWith(MockitoExtension.class)
public class MockJwtDecoderJUnitTest {
@Mock
private JwtDecoder jwtDecoder;
@InjectMocks
private UserController userController;
@BeforeEach
void setUp() {
SecurityContextHolder.clearContext();
}
}
In this setup, we use @ExtendWith(MockitoExtension.class) to enable Mockito in our JUnit tests. The JwtDecoder is mocked using @Mock, and the UserController is injected with the mocked JwtDecoder using @InjectMocks. The SecurityContextHolder is cleared before each test to ensure a clean state.
3. Mocking JWT Decoding
With our environment setup, we write tests to mock JWT decoding. We start by testing a valid JWT token.
3.1. Testing a Valid Token
The application should return the user information when a valid token is provided. Here’s how we test this scenario:
@Test
void whenValidToken_thenReturnsUserInfo() {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "john.doe");
Jwt jwt = Jwt.withTokenValue("token")
.header("alg", "none")
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
JwtAuthenticationToken authentication = new JwtAuthenticationToken(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
ResponseEntity<String> response = userController.getUserInfo(jwt);
assertEquals("Hello, john.doe", response.getBody());
assertEquals(HttpStatus.OK, response.getStatusCode());
}
In this test, we create a mock JWT with a sub (subject) claim. The JwtAuthenticationToken is used to set up the security context, and the UserController processes the token and returns a response. We verify the response using assertions.
3.2. Testing Custom Claims
Sometimes, JWTs contain custom claims such as roles or email addresses. For example, if the UserController uses the roles claim to authorize access, the test should check that the controller behaves as expected based on the claimed roles:
@Test
void whenTokenHasCustomClaims_thenProcessesCorrectly() {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "john.doe");
claims.put("roles", Arrays.asList("ROLE_USER", "ROLE_ADMIN"));
claims.put("email", "[email protected]");
Jwt jwt = Jwt.withTokenValue("token")
.header("alg", "none")
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
List authorities = ((List) jwt.getClaim("roles"))
.stream()
.map(role -> new SimpleGrantedAuthority(role))
.collect(Collectors.toList());
JwtAuthenticationToken authentication = new JwtAuthenticationToken(
jwt,
authorities,
jwt.getClaim("sub")
);
SecurityContextHolder.getContext().setAuthentication(authentication);
ResponseEntity response = userController.getUserInfo(jwt);
assertEquals("Hello, john.doe", response.getBody());
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(authentication.getAuthorities().stream()
.anyMatch(auth -> auth.getAuthority().equals("ROLE_ADMIN")));
}
In this test, we verify that the roles claim is correctly processed and that the user has the expected authorities (in this case, ROLE_ADMIN).
4. Testing Other Scenarios
Next, we explore testing different cases.
4.1. Testing an Invalid Token
When an invalid token is provided, the application should throw a JwtValidationException. Let’s write a quick test to verify that the JwtDecoder properly throws an exception when attempting to decode an invalid token:
@Test
void whenInvalidToken_thenThrowsException() {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", null);
Jwt invalidJwt = Jwt.withTokenValue("invalid_token")
.header("alg", "none")
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
JwtAuthenticationToken authentication = new JwtAuthenticationToken(invalidJwt);
SecurityContextHolder.getContext()
.setAuthentication(authentication);
JwtValidationException exception = assertThrows(JwtValidationException.class, () -> {
userController.getUserInfo(invalidJwt);
});
assertEquals("Invalid token", exception.getMessage());
}
In this test, we mock JwtDecoder to throw a JwtValidationException when a null token is processed.
The test asserts that a JwtValidationException is thrown with the message “Invalid token“.
4.2. Testing an Expired Token
When an expired token is provided, the application should throw a JwtValidationException. The test below verifies that the JwtDecoder properly throws an exception when attempting to decode an expired token:
@Test
void whenExpiredToken_thenThrowsException() throws Exception {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "john.doe");
claims.put("exp", Instant.now().minus(1, ChronoUnit.DAYS));
Jwt expiredJwt = Jwt.withTokenValue("expired_token")
.header("alg", "none")
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
JwtAuthenticationToken authentication = new JwtAuthenticationToken(expiredJwt);
SecurityContextHolder.getContext()
.setAuthentication(authentication);
JwtValidationException exception = assertThrows(JwtValidationException.class, () -> {
userController.getUserInfo(expiredJwt);
});
assertEquals("Token has expired", exception.getMessage());
}
In this test, we set the expiration to 1 day in the past to simulate an expired token.
The test asserts that a JwtValidationException is thrown with the message “Token has expired“.
5. Conclusion
In this tutorial, we’ve learned how to mock JWT decoding in JUnit tests using Mockito. We covered various scenarios, including testing valid tokens with custom claims, handling invalid tokens, and managing expired tokens.
By mocking JWT decoding, we can write unit tests for spring security applications without relying on external token generation or validation services. This approach ensures that our tests are fast, reliable, and independent of external dependencies.
The code backing this article is available on GitHub. Once you're
logged in as a Baeldung Pro Member, start learning and coding on the project.