In the previous article, we integrated the Razorpay payment gateway with our Spring Boot Angular application. Now, we are gonna unit test one of the REST controller using Mockito.
Introduction
JUnit is an open-source unit testing framework for Java that is used to write and run repeatable automated tests. Unit testing is one of the best test methods for regression testing.
Mockito is an open-source testing framework for Java that allows the creation of test double objects in automated unit tests for the purpose of test-driven development or behavior-driven development.
Implementation
Add Dependencies
Let’s add the spring-security-test dependency to our pom.xml since it is not part of the spring-boot-starter-test dependency. We need this dependency to create a MockCustomUserSecurityContextFactory for the Junit tests since some of the API endpoints that we are going to unit test have method-level security.
pom.xml
<dependency>	<groupId>org.springframework.security</groupId>	<artifactId>spring-security-test</artifactId>	<scope>test</scope> </dependency> Create Utility Class
This utility class is responsible for creating the User Entity mocks that will be used for creating request objects and mock security contexts.
MockUserUtils.java
package com.javachinna.config; import java.util.Set; import com.javachinna.model.Role; import com.javachinna.model.User; public class MockUserUtils {	private MockUserUtils() {	}	/** * */	public static User getMockUser(String username) {	User user = new User();	user.setId(1L);	user.setEmail(username);	user.setPassword("secret");	Role role = new Role();	role.setName(Role.ROLE_PRE_VERIFICATION_USER);	user.setRoles(Set.of(role));	user.setEnabled(true);	user.setSecret("secret");	return user;	} } Test with Mock User
Using @WithMockUser Annotation
 The code verification REST API is having method-based security. So, only authenticated users with PRE_VERIFICATION_USER role can access this endpoint.
If we are not using a custom Authentication principal, then we can use @WithMockUser annotation to run the test as a specific user with PRE_VERIFICATION_USER role as shown below.
@Test @WithMockUser(username="chinna",roles={"PRE_VERIFICATION_USER"}) public void testVerifyCodeWhenCodeIsValid() { ... } Or with just roles
@Test @WithMockUser(roles={"PRE_VERIFICATION_USER"}) public void testVerifyCodeWhenCodeIsValid() { ... } Or we can also place the annotation at the class level and every test will use the specified user.  So that, we don’t need to annotate each test with @WithMockUser annotation
@SpringBootTest @AutoConfigureMockMvc @WithMockUser(username="admin",roles={"USER","ADMIN","PRE_VERIFICATION_USER"}) class AuthControllerTest { ... } If we are using a custom Authentication principal, then there are 2 options. Either we can use @WithUserDetails annotation or we can create our own custom annotation
Using @WithUserDetails Annotation
 The custom principal is often times returned by a custom UserDetailsService that returns an object that implements both UserDetails and the custom type. In our case, it is LocalUser which extends org.springframework.security.core.userdetails.User and implements OAuth2User & OidcUser for social authentication support.
For situations like this, it is useful to create the test user using the custom UserDetailsService. That is exactly what @WithUserDetails does.
@WithUserDetails would allow us to use a custom UserDetailsService to create our Authentication principal but it requires the user to exist in the database.
@Test @WithUserDetails(value="customUsername", userDetailsServiceBeanName="localUserDetailService") public void testVerifyCodeWhenCodeIsValid() { ... } This test would look up the username of customUsername using the UserDetailsService with the bean name localUserDetailService. Both value and userDetailsServiceBeanName fields are optional. If we don’t specify, then this test would look up the username of user using the UserDetailsService
Using Custom Annotation
We can create our own annotation that uses the @WithSecurityContext to create any SecurityContext we want. For example, we might create an annotation named @WithMockCustomUser as shown below:
WithMockCustomUser.java
package com.javachinna.config; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.springframework.security.test.context.support.WithSecurityContext; @Retention(RetentionPolicy.RUNTIME) @WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) public @interface WithMockCustomUser {	String username() default "JavaChinna"; } You can see that @WithMockCustomUser is annotated with the @WithSecurityContext annotation. This is what signals to Spring Security Test support that we intend to create a SecurityContext for the test. The @WithSecurityContext annotation requires we specify a SecurityContextFactory that will create a new SecurityContext given our @WithMockCustomUser annotation. You can find our WithMockCustomUserSecurityContextFactory implementation below:
WithMockCustomUserSecurityContextFactory.java
package com.javachinna.config; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.test.context.support.WithSecurityContextFactory; import com.javachinna.dto.LocalUser; import com.javachinna.model.User; public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {	@Override	public SecurityContext createSecurityContext(WithMockCustomUser customUser) {	SecurityContext context = SecurityContextHolder.createEmptyContext();	User user = MockUserUtils.getMockUser(customUser.username());	LocalUser localUser = LocalUser.create(user, null, null, null);	Authentication auth = new UsernamePasswordAuthenticationToken(localUser, user.getPassword(), localUser.getAuthorities());	context.setAuthentication(auth);	return context;	} } We can now annotate a test class or a test method with our new annotation and Spring Security’s WithSecurityContextTestExecutionListener will ensure that our SecurityContext is populated appropriately.
Unit Testing Authentication Controller
Since application security is one of the critical aspects of an application, it’s our responsibility to unit test to make sure that it is defect-free and working as expected. Hence, we are gonna write some unit test cases for our AuthConroller in this Spring Boot + Angular + MySQL Maven Application.
The AuthConroller exposes 3 POST APIs for User Login, Registration, and TOTP verification requests. Let’s create AuthConrollerTest class to unit test these 3 endpoints.
AuthControllerTest.java
@SpringBootTest annotation can be specified on a test class that runs Spring Boot based tests. It provides the following features over and above the regular Spring TestContext Framework:
- Uses SpringBootContextLoaderas the defaultContextLoaderwhen no specific@ContextConfiguration(loader=...)is defined.
- Automatically searches for a @SpringBootConfigurationwhen nested@Configurationis not used, and no explicitclassesare specified.
- Allows custom Environmentproperties to be defined using theproperties attribute.
- Allows application arguments to be defined using the args attribute.
- Provides support for different webEnvironmentmodes, including the ability to start a fully running web server listening on adefinedorrandomport.
- Registers a TestRestTemplateand/orWebTestClientbean for use in web tests that are using a fully running web server.
@AutoConfigureMockMvc annotation can be applied to a test class to enable and configure auto-configuration of MockMvc which provides the server-side Spring MVC test support. 
Note: You can also use @WebMvcTest annotation that focuses on Spring MVC components. This annotation will disable full auto-configuration and only apply configuration relevant to MVC tests. If you are looking to load your full application configuration and use MockMVC, you should consider @SpringBootTest combining with @AutoConfigureMockMvc rather than this annotation.
@MockBean annotation can be used to add mocks to a Spring ApplicationContext. It can be used as a class-level annotation or on fields in either @Configuration classes or test classes that are @RunWith the SpringRunner. Mocks can be registered by type or by bean name. Any existing single bean of the same type defined in the context will be replaced by the mock. If no existing bean is defined a new one will be added.
package com.javachinna.controller; import static org.mockito.ArgumentMatchers.any; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.test.web.servlet.MockMvc; import com.fasterxml.jackson.databind.ObjectMapper; import com.javachinna.config.MockUserUtils; import com.javachinna.config.WithMockCustomUser; import com.javachinna.dto.LocalUser; import com.javachinna.dto.LoginRequest; import com.javachinna.dto.SignUpRequest; import com.javachinna.dto.SocialProvider; import com.javachinna.exception.UserAlreadyExistAuthenticationException; import com.javachinna.model.User; import com.javachinna.service.UserService; import dev.samstevens.totp.code.CodeVerifier;; @SpringBootTest @AutoConfigureMockMvc class AuthControllerTest {	@Autowired	private MockMvc mockMvc;	@MockBean	private UserService userService;	@MockBean	private CodeVerifier verifier;	@MockBean	private AuthenticationManager authenticationManager;	private static User user = MockUserUtils.getMockUser("JavaChinna");	private static ObjectMapper mapper = new ObjectMapper();	@Test	public void testAuthenticateUser() throws Exception {	LocalUser localUser = LocalUser.create(user, null, null, null);	LoginRequest loginRequest = new LoginRequest(user.getEmail(), user.getPassword());	UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(localUser, null);	Mockito.when(authenticationManager.authenticate(Mockito.any(UsernamePasswordAuthenticationToken.class))).thenReturn(authentication);	String json = mapper.writeValueAsString(loginRequest);	mockMvc.perform(post("/api/auth/signin").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("true")).andExpect(jsonPath("$.accessToken").isNotEmpty());	// Test when user 2fa is enabled	user.setUsing2FA(true);	mockMvc.perform(post("/api/auth/signin").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("false")).andExpect(jsonPath("$.user").doesNotExist());	}	@Test	public void testRegisterUser() throws Exception {	SignUpRequest signUpRequest = new SignUpRequest("1234", "JavaChinna", user.getEmail(), user.getPassword(), user.getPassword(), SocialProvider.FACEBOOK);	Mockito.when(userService.registerNewUser(any(SignUpRequest.class))).thenReturn(user);	String json = mapper.writeValueAsString(signUpRequest);	mockMvc.perform(post("/api/auth/signup").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isOk()).andExpect(jsonPath("$.success").value("true")).andExpect(jsonPath("$.message").value("User registered successfully"));	// Test when user provided email already exists in the database	Mockito.when(userService.registerNewUser(any(SignUpRequest.class))).thenThrow(new UserAlreadyExistAuthenticationException("exists"));	json = mapper.writeValueAsString(signUpRequest);	mockMvc.perform(post("/api/auth/signup").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isBadRequest()).andExpect(jsonPath("$.success").value("false")).andExpect(jsonPath("$.message").value("Email Address already in use!"));	}	@Test	@WithMockCustomUser	public void testVerifyCodeWhenCodeIsNotValid() throws Exception {	Mockito.when(verifier.isValidCode(Mockito.anyString(), Mockito.anyString())).thenReturn(false);	String json = mapper.writeValueAsString("443322");	mockMvc.perform(post("/api/auth/verify").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isBadRequest()).andExpect(jsonPath("$.success").value("false")).andExpect(jsonPath("$.message").value("Invalid Code!"));	}	@Test	@WithMockCustomUser	public void testVerifyCodeWhenCodeIsValid() throws Exception {	Mockito.when(verifier.isValidCode(Mockito.anyString(), Mockito.anyString())).thenReturn(true);	String json = mapper.writeValueAsString("443322");	mockMvc.perform(post("/api/auth/verify").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("true")).andExpect(jsonPath("$.accessToken").isNotEmpty())	.andExpect(jsonPath("$.user").exists());	} } Note: If you are looking for a simple solution and don’t want to use any of the Mock User/Custom annotations to test the REST controller, then you can have a look at Unit Test REST Controller with Spring Security using Mock Authentication which is just a modified version of the same AuthControllerTest class.
Now, let’s dive deep into each method and see what it does for a better understanding in the following sections.
Login API Unit Test Cases
This test method is responsible for unit testing the SignIn API. It covers the following 2 scenarios.
- Test when 2FA is not enabled. - Expected result: HTTP Status 200 Ok response with access tokenandauthenticated=truein the response body.
 
- Expected result: HTTP Status 200 Ok response with 
- Test when 2FA is enabled.- Expected result: HTTP Status 200 Ok response with authenticated=falsein the response body.
 
- Expected result: HTTP Status 200 Ok response with 
To keep it simple, I have covered both use cases in the same test method. However, it is always a good practice to have one test method for one use case as per the single-responsibility principle (SRP).
@Test	public void testAuthenticateUser() throws Exception {	LocalUser localUser = LocalUser.create(user, null, null, null);	LoginRequest loginRequest = new LoginRequest(user.getEmail(), user.getPassword());	UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(localUser, null);	Mockito.when(authenticationManager.authenticate(Mockito.any(UsernamePasswordAuthenticationToken.class))).thenReturn(authentication);	String json = mapper.writeValueAsString(loginRequest);	mockMvc.perform(post("/api/auth/signin").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("true")).andExpect(jsonPath("$.accessToken").isNotEmpty());	// Test when user 2fa is enabled	user.setUsing2FA(true);	mockMvc.perform(post("/api/auth/signin").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("false")).andExpect(jsonPath("$.user").doesNotExist());	} Points to be noted:
- Mockito.when()method enables stubbing methods. Use it when you want the mock to return a particular value when a particular method is called. Simply put: “When the x method is called then return y”.- Login API makes use of AuthenticationManager.authenticate()method to perform the authentication. Since we don’t want to call this actual method during our test, we have mocked theAuthenticationManagerusing@MockBeanannotation and stubbed theauthenticate()method call withMockito.when()method. So that, whenever this method is called, Mockito will return our mockauthenticationobject.
 
- Login API makes use of 
- MockMvc.perform()method used to perform a request and return a type that allows chaining further actions, such as asserting expectations, on the result.
- MockMvcRequestBuilders.post()method is used to build an HTTP post request
- ResultActions.andExpect()method is used to perform an expectation on the returned response
- MockMvcResultMatchers.jsonPath()method that allows access to response body assertions using a JsonPath expression to inspect a specific subset of the body. The JSON path expression can be a parameterized string using formatting specifiers as defined in- String.format(String, Object).
User Registration API Unit Test Cases
This test method is responsible for unit testing the SignUP API. It covers the following 2 scenarios.
- Test when user-provided email does not exist in the database- Expected result: HTTP Status 200 Ok response with successmessage in the response body.
 
- Expected result: HTTP Status 200 Ok response with 
- Test when user-provided email already exists in the database- Expected result: HTTP Status 400 BadRequest response with errormessage in the response body.
 
- Expected result: HTTP Status 400 BadRequest response with 
@Test	public void testRegisterUser() throws Exception {	SignUpRequest signUpRequest = new SignUpRequest("1234", "JavaChinna", user.getEmail(), user.getPassword(), user.getPassword(), SocialProvider.FACEBOOK);	Mockito.when(userService.registerNewUser(any(SignUpRequest.class))).thenReturn(user);	String json = mapper.writeValueAsString(signUpRequest);	mockMvc.perform(post("/api/auth/signup").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isOk()).andExpect(jsonPath("$.success").value("true")).andExpect(jsonPath("$.message").value("User registered successfully"));	// Test when user provided email already exists in the database	Mockito.when(userService.registerNewUser(any(SignUpRequest.class))).thenThrow(new UserAlreadyExistAuthenticationException("exists"));	json = mapper.writeValueAsString(signUpRequest);	mockMvc.perform(post("/api/auth/signup").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isBadRequest()).andExpect(jsonPath("$.success").value("false")).andExpect(jsonPath("$.message").value("Email Address already in use!"));	} Code Verification API Unit Test Cases
This test method is responsible for unit testing the Verify API. It tests if the application returns a HTTP Status 400 BadRequest with Invalid Code error message in the response when the code is not valid.
@Test	@WithMockCustomUser	public void testVerifyCodeWhenCodeIsNotValid() throws Exception {	Mockito.when(verifier.isValidCode(Mockito.anyString(), Mockito.anyString())).thenReturn(false);	String json = mapper.writeValueAsString("443322");	mockMvc.perform(post("/api/auth/verify").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isBadRequest()).andExpect(jsonPath("$.success").value("false")).andExpect(jsonPath("$.message").value("Invalid Code!"));	} This test checks if the application returns a HTTP Status 200 Ok with authenticated=true in the response when the code is valid.
@Test	@WithMockCustomUser	public void testVerifyCodeWhenCodeIsValid() throws Exception {	Mockito.when(verifier.isValidCode(Mockito.anyString(), Mockito.anyString())).thenReturn(true);	String json = mapper.writeValueAsString("443322");	mockMvc.perform(post("/api/auth/verify").contentType(MediaType.APPLICATION_JSON).characterEncoding("utf-8").content(json).accept(MediaType.APPLICATION_JSON))	.andExpect(status().isOk()).andExpect(jsonPath("$.authenticated").value("true")).andExpect(jsonPath("$.accessToken").isNotEmpty())	.andExpect(jsonPath("$.user").exists());	} Run JUnit Tests

References
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#test-method-withmockuser
Source Code
https://github.com/JavaChinna/angular-spring-boot-razorpay-integration
Conclusion
That’s all folks. In this article, we have implemented unit test cases for our REST controller using Junit 5 and Mockito.
Thank you for reading.



