How to perform unit testing for Controllers and Services

As you may be well aware, testing is very important. In this tutorial therefore, you will learn how to do just that! Testing. And more specifically, you will learn how to perform that testing on Controllers and Services.

java-featured-image

Controllers

Normal controller does 1 of 2 things:

  • renders a view

or

  • handles form submission

Let’s look at a code example:

@RestController
@RequestMapping("/employee/account/*")
public class EmployeeAccountController {
	private EmployeeService employeeService;
	
	// define the logger to which we will be writing stuff for debugging purposes
	private static Logger log = LoggerFactory.getLogger(EmployeeAccountController.class);
	@Autowired
	public EmployeeAccountController(EmployeeService employeeService) {
		this.employeeService = employeeService;
	}
	
	@PostMapping(value="/register/process", produces="application/json")
	public Response registrationProcess(ModelMap model, @RequestBody Employee reqEmployee) {
		Employee employee = null;
		
		try {
			// try to validate the user using the validate() function defined in the EmployeeService class
			employee = employeeService.validate(employee.getEmail(), employee.getUsername(), employee.getPassword());
		}
		catch (Exception e) {
			// if the given information did not match the validate() method criteria then print it out to the console
			log.debug(e.getMessage(), e);
		}
		
		// return appropriate string message
		return employee != null ? new Response("Successful registration.") : new Response("Unsuccessful registration."); 
	}
}

By looking at the above code snippet, you will see that this is just a normal Controller that does a validity check on information given in the form from the user. In the end, it returns a string that is either “successful message” or “unsuccessful message” based on the response from the validity check.

Great! But it’s missing something. And that something is Unit testing! Let’s add it.

Before I show you the code however, I want to explain to you what MockMvc is. It is a Spring MVC components that is used mainly for creating units tests for controller components. An example code snippet of how to use MockMvc:

private MockMvc name;

@MockBean
private Service service;

@BeforeEach
public string doSomething() throws Exception {
   service = MockMvcBuilders.standaloneSetup(className(service)).build();
}

The @MockBean annotation helps us mock dependencies within a controller.



EmployeeAccountControllerTest.java

@ExtendWith(SpringExtension.class)
@Tag("Controller")
public class EmployeeAccountControllerTest {
	private MockMvc mockMvc;
	
	@MockBean
	private EmployeeService employeeService;
	
	// define the logger to which we will be writing stuff for debugging purposes
	@BeforeEach
	public void test(TestInfo info) throws Exception {
	   mockMvc = MockMvcBuilders.standaloneSetup(new EmployeeAccountController(employeeService)).build();
	}
	
	@Test
	@DisplayName("Return some error message text")
	public void ReturnErrorMessage() throws Exception {
	   Employee emp = new Employee();
	   emp.setEmail("demo@demo.com");
	   emp.setUsername("demoname");
	   emp.setPassword("demo123");
	   
	   Gson gSon = new Gson();
	   String gsonEmployee = gSon.toJson(emp);
	   
	   Mockito.when(employeeService.validate("demo@demo.com", "demoname", "demo123")).thenReturn(null);
	   
	   // the result
	   MvcResult result = mockMvc.perform(post("/employee/account/register/process").contentType(MediaType.APPLICATION_JSON).content(gsonEmployee)).andExpect(status().isOk()).andReturn();
	   
	   MockHttpServletResponse response = result.getResponse();
	   ObjectMapper mapper = new ObjectMapper();
	   Response responseString = mapper.readValue(response.getContentAsString(), Response.class);
	   assertTrue(responseString.getCode().equals("Successful registration."));
	   assertTrue(responseString.getMessage().equals("Please try again!"));
	}
}

Based on the name of the class, you already know that this is test class for the controller.

Breakdown

  • @BeforeEach: the code which is run before each unit test
  • @Test: the unit test itself
  • @DisplayName: the JUnit5 annotation for assigning a descriptive text for unit tests
  • @Tag: can be used to test discovery and execution
  • @ExtendWith(.class): integrates the Spring 5 Test context framework with JUnit 5

So in the code above, first we are using the @ExtendWith annotation which we use to integrate the Spring 5 Test framework with JUnit 5. Then We use the @Tag annotation which we use to specify that this is the Test class. Then we use the @Mockbean annotation which again, helps us mock dependencies. Then we use the @BeforeEach annotation which represents the code that is about to be run before each unit test. In our case, building the employee service. Afterwards, we use the @Test annotation which specifies that the following method is the Test and then we also use the @DisplayName annotation which as mentioned above, gives a descriptive text for the unit test method (in the code snippet is not the most descriptive but it’ll do).

Within the method, we are converting the Employee instance to JSON and then we set the Mock behavior to return null every time validate() is called. And then we invoke the controller method.

Services

In the Controller testing example, I used EmployeeService as the service. Now in this sub-section, we will see the implementation of that service.

EmployeeServiceImpl.java

@Service
public class EmployeeServiceImpl implements EmployeeService {
	private EmployeeDAO employeeDAO;
	
        @Autowired
	public EmployeeServiceImpl(EmployeeDAO empDAO) {
		employeeDAO = empDAO;
	}
	
	@Override
	public void update(Employee emp) {
		employeeDAO.update(emp);
	}
	
	// method that checks whether the employee exists or not
	public boolean exists(String username) throws Exception {
		List<Employee> employees = (List<Employee>) employeeDAO.findByUsername(username);
		
		// check if there are employees matching the given username
		if (employees.getSize() != 0) {
		     return true;
                }

                // throw exception
                throw new Exception("Employee does not exist in database.");
			
		// return false if there are no matches
		return false;
	}
}

Breakdown

The class implementation is pretty straightforward – first, we create a constructor that takes in an Employee data acess object (DAO) and is used to be assigned to our private member variable. Then we have got an update(Employee) method which does just that – updates the employee record based on the information provided as a parameter (the Employee). And lastly, we have got the exists(String) method that checks whether an employee with the specified username exists. Returns either true or false.

Now, let’s create the test class for the implementation.

EmployeeServiceTest.java

@ExtendWith(SpringExtension.class)
@Tag("Service")
public class EmployeeServiceTest {
	@MockBean
	private EmployeeDAO employeeDAO;
	private EmployeeService employeeService;
	
	// code run before unit tests
	@BeforeEach
	public void test(TestInfo info) throws Exception {
		employeeService = new EmployeeServiceImpl(employeeDAO);
		assertTrue(testInfo.getDisplayName().equals("Error message"));
	}
	
	@TestInfo
	@DisplayName("Error message")
	public void throwException_When_EmployeeDoesNotExist() {
		String username = "employee123";
		Mockito.when(employeeDao.findByUsername(username)).thenReturn(new ArrayList<User>());
		
		assertThatThrownBy(() -> employeeService.exists(username)).isInstanceOf(Exception.class).hasMessage("Employee does not exist in database.");
	}
}

Breakdown

We create a Test class for EmployeeService. Before each unit test, we run the test(TestInfo) method and then we run the throwException_When_EmployeeDoesNotExist() method.

Leave a Reply

avatar