
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. 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.
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. We test how our application handles these custom claims:
@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", "john.doe@example.com");
Jwt jwt = Jwt.withTokenValue("token")
.header("alg", "none")
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
ResponseEntity<String> response = userController.getUserInfo(jwt);
assertEquals("Hello, john.doe", response.getBody());
assertEquals(HttpStatus.OK, response.getStatusCode());
}
In this test, we add custom claims (role and email) to JWT. The controller processes the token and returns the expected response.
4. Handling Different Scenarios
4.1. Testing an Invalid Token
When an invalid token is provided, the application should throw a JwtValidationException. Here’s how we test this scenario:
@Test
void whenInvalidToken_thenThrowsException() {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "invalid.user");
Jwt invalidJwt = Jwt.withTokenValue("invalid_token")
.header("alg", "none")
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
when(jwtDecoder.decode("invalid_token"))
.thenThrow(new JwtValidationException(
"Invalid token",
Arrays.asList(new OAuth2Error("invalid_token"))
));
JwtValidationException thrown = assertThrows(
JwtValidationException.class,
() -> jwtDecoder.decode("invalid_token")
);
assertEquals("Invalid token", thrown.getMessage());
}
In this test, we mock the JwtDecoder to throw a JwtValidationException when an invalid token is decoded. We verify that the exception is thrown with the correct message.
4.2. Testing an Expired Token
In this scenario, when an expired token is provided, the application should also throw a JwtValidationException. Here’s how we test this scenario:
@Test
void whenTokenExpired_thenThrowsException() {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "expired.user");
claims.put("exp", Instant.now().minusSeconds(3600));
claims.put("iat", Instant.now().minusSeconds(7200));
Jwt expiredJwt = Jwt.withTokenValue("expired_token")
.header("alg", "none")
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
when(jwtDecoder.decode("expired_token"))
.thenThrow(new JwtValidationException(
"Token expired",
Arrays.asList(new OAuth2Error("invalid_token"))
));
JwtValidationException thrown = assertThrows(
JwtValidationException.class,
() -> jwtDecoder.decode("expired_token")
);
assertEquals("Token expired", thrown.getMessage());
}
In this test, we create a JWT with an expiration time (exp) in the past. We mock the JwtDecoder to throw a JwtValidationException when the expired token is decoded. We verify that the exception is thrown with the correct message.
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 complete source code for this article is available over on GitHub.
The post Mock JWT with JwtDecoder in JUnit Test first appeared on Baeldung.