In a Spring Boot project, we have to test the web layer. For that, we can use MockMVC. In this tutorial, let us see how to do that by having a sample GeekEmployee bean and writing the business logic as well as the test cases for it.
Example Project
Project Structure:
This is a maven project. Let’s start with
pom.xml
XML
<? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 < modelVersion >4.0.0</ modelVersion > < parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > < version >2.3.4.RELEASE</ version > < relativePath /> </ parent > < groupId >com.gfg</ groupId > < artifactId >test-springmvc</ artifactId > < version >0.0.1-SNAPSHOT</ version > < name >test-springmvc</ name > < description >Sample Spring Boot</ description > < properties > < java.version >11</ java.version > </ properties > < dependencies > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-thymeleaf</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > < exclusions > < exclusion > < groupId >org.junit.vintage</ groupId > < artifactId >junit-vintage-engine</ artifactId > </ exclusion > </ exclusions > </ dependency > </ dependencies > < build > < plugins > < plugin > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-maven-plugin</ artifactId > </ plugin > </ plugins > </ build > </ project > |
First, let’s go with the bean class
GeekEmployee.java
Java
public class GeekEmployee { private Long employeeId; private String firstName; private String lastName; private int salary; public int getSalary() { return salary; } public void setSalary( int salary) { this .salary = salary; } public GeekEmployee(String firstName, String lastName, int salary) { this .firstName = firstName; this .lastName = lastName; this .salary = salary; } public Long getEmployeeId() { return employeeId; } public void setEmployeeId(Long employeeId) { this .employeeId = employeeId; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this .firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this .lastName = lastName; } } |
As employeeId is auto-generated, let us create that via
GeekEmployeeIdGenerator.java
Java
public class GeekEmployeeIdGenerator { private static long employeeId = 1000 ; public static synchronized long value() { return employeeId++; } } |
Service file where we can write our business logic
GeekEmployeeService.java
Java
import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Optional; import org.springframework.stereotype.Service; import com.gfg.gfgsample.domain.GeekEmployee; import com.gfg.gfgsample.util.GeekEmployeeIdGenerator; @Service public class GeekEmployeeService { Map<Long, GeekEmployee> geekEmployees = new HashMap<>(); // Return all geekEmployees public Collection<GeekEmployee> findAll(){ return geekEmployees.values(); } // Find the geekEmployee with this id public Optional<GeekEmployee> findById(Long employeeId) { GeekEmployee geekEmployee = null ; if (geekEmployees.containsKey(employeeId)) geekEmployee = geekEmployees.get(employeeId); return Optional.ofNullable(geekEmployee); } // Save a new GeekEmployee public GeekEmployee save(GeekEmployee geekEmployee) { geekEmployee.setEmployeeId(GeekEmployeeIdGenerator.value()); geekEmployees.put(geekEmployee.getEmployeeId(), geekEmployee); return geekEmployee; } // Update the GeekEmployee with this id public Optional<GeekEmployee> update(GeekEmployee geekEmployee) { GeekEmployee geekEmployee1 = geekEmployees.get(geekEmployee.getEmployeeId()); if (geekEmployee1 != null ) { geekEmployees.put(geekEmployee.getEmployeeId(), geekEmployee); geekEmployee1 = geekEmployees.get(geekEmployee.getEmployeeId()); } return Optional.ofNullable(geekEmployee1); } // Delete GeekEmployee with this id public Optional<GeekEmployee> delete(Long employeeId) { GeekEmployee geekEmployee1 = geekEmployees.get(employeeId); if (geekEmployee1 != null ) { geekEmployees.remove(employeeId); } return Optional.ofNullable(geekEmployee1); } } |
GeekEmployeeMvcController.java
Java
package com.gfg.gfgsample.web; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.gfg.gfgsample.service.GeekEmployeeService; @Controller @RequestMapping ( "mvc" ) public class GeekEmployeeMvcController { private final GeekEmployeeService geekEmployeeService; public GeekEmployeeMvcController(GeekEmployeeService geekEmployeeService) { this .geekEmployeeService = geekEmployeeService; } @GetMapping ( "geekemployees" ) public String getGeekEmployees(Model model) { model.addAttribute( "geekemployees" , geekEmployeeService.findAll()); return "geekemployee-list" ; } } |
GeekEmployeeRestController.java
Java
import java.net.URI; import java.util.Collection; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import com.gfg.gfgsample.domain.GeekEmployee; import com.gfg.gfgsample.service.GeekEmployeeService; import com.gfg.gfgsample.util.GeekEmployeeIdGenerator; @RestController @RequestMapping ( "geekemployees" ) public class GeekEmployeeRestController { private final GeekEmployeeService geekEmployeeService; public GeekEmployeeRestController(GeekEmployeeService service) { this .geekEmployeeService = service; } @GetMapping Collection<GeekEmployee> readGeekEmployees(){ return this .geekEmployeeService.findAll(); } @GetMapping ( "/{id}" ) GeekEmployee readGeekEmployee( @PathVariable Long id) { return this .geekEmployeeService.findById(id) .orElseThrow(GeekEmployeeNotFoundException:: new ); } @PostMapping ResponseEntity<?> addEmployee( @RequestBody GeekEmployee geekEmployee){ // Hack to get Mockito test to work // Will fix this soon // When not running JUnit tests // These statements should be commented out // and the statements below should be uncommented this .geekEmployeeService.save(geekEmployee); URI location = ServletUriComponentsBuilder .fromCurrentRequest() .path( "/{id}" ) .buildAndExpand(GeekEmployeeIdGenerator.value()) .toUri(); return ResponseEntity.created(location).build(); } @PutMapping GeekEmployee updateEmployee( @RequestBody GeekEmployee geekEmployee) { return this .geekEmployeeService.update(geekEmployee) .orElseThrow(GeekEmployeeNotFoundException:: new ); } @DeleteMapping ( "/{id}" ) void deleteStudent( @PathVariable Long id) { this .geekEmployeeService.delete(id) .orElseThrow(GeekEmployeeNotFoundException:: new ); } @ResponseStatus (HttpStatus.NOT_FOUND) class GeekEmployeeNotFoundException extends RuntimeException { private static final long serialVersionUID = 1L; public GeekEmployeeNotFoundException() { super ( "Employee does not exist" ); } } } |
Start up the file that can run as the java application
TestSpringmvcApplication.java
Java
import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import com.gfg.gfgsample.domain.GeekEmployee; import com.gfg.gfgsample.service.GeekEmployeeService; @SpringBootApplication public class TestSpringmvcApplication { public static void main(String[] args) { SpringApplication.run(TestSpringmvcApplication. class , args); } @Bean CommandLineRunner init(GeekEmployeeService geekEmployeeService) { return args -> { geekEmployeeService.save( new GeekEmployee( "Rachel" , "Green" , 100000 )); geekEmployeeService.save( new GeekEmployee( "Monica" , "Geller" , 40000 )); geekEmployeeService.save( new GeekEmployee( "Phoebe" , "" , 45000 )); }; } } |
geekemployee-list.html
HTML
< head > < title >Employee List</ title > <!-- Latest compiled and minified CSS --> < link rel = "stylesheet" integrity = "sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin = "anonymous" /> </ head > < body > < div class = "container" > < div class = "page-header" > < h1 >Employee List</ h1 > </ div > < div class = "container" > < div class = "column" > < table class = "table datatable" > < tr > < th >First Name</ th > < th >Last Name</ th > < th >Salary</ th > </ tr > < tr th:each = "geekemployee : ${geekemployees}" > < td th:text = "${geekemployee.firstName}" >Joe</ td > < td th:text = "${geekemployee.lastName}" >Tribiani</ td > < td th:text = "${geekemployee.salary}" >100000</ td > </ tr > </ table > </ div > </ div > </ div > </ body > </ html > |
After running the spring application, our console is as follows
Output on mvc/geekemployees
Testing Part:
GeekEmployeeMvcWebTest.java
Java
import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; import java.util.List; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import com.gfg.gfgsample.domain.GeekEmployee; import com.gfg.gfgsample.service.GeekEmployeeService; @WebMvcTest (controllers = GeekEmployeeMvcController. class ) class GeekEmployeeMvcWebTest { @Autowired MockMvc mockMvc; @MockBean GeekEmployeeService geekEmployeeService; @Test void checkForGeekEmployeeListView() throws Exception { GeekEmployee ge1 = new GeekEmployee( "Rachel" , "Green" , 50000 ); GeekEmployee ge2 = new GeekEmployee( "Monica" , "Geller" , 40000 ); GeekEmployee ge3 = new GeekEmployee( "Phoebe" , "" , 45000 ); List<GeekEmployee> geekEmployeeList = List.of(ge1, ge2, ge3); when(geekEmployeeService.findAll()).thenReturn(geekEmployeeList); this .mockMvc.perform(get( "/mvc/geekemployees" )) .andExpect(status().isOk()) .andExpect(view().name( "geekemployee-list" )) .andExpect(model().attribute( "geekemployees" , geekEmployeeList)) .andExpect(model().attribute( "geekemployees" , Matchers.hasSize( 3 ))) .andDo(print()); } } |
Testcase Output:
GeekEmployeeRestWebTest.java
Java
import static org.hamcrest.CoreMatchers.containsString; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.Optional; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import com.gfg.gfgsample.domain.GeekEmployee; import com.gfg.gfgsample.service.GeekEmployeeService; @WebMvcTest (controllers = GeekEmployeeRestController. class ) class GeekEmployeeRestWebTest { @Autowired MockMvc mockMvc; @MockBean GeekEmployeeService geekEmployeeService; @Test void whenReadGeekEmployee_returnJsonContent() throws Exception { GeekEmployee rachel = new GeekEmployee( "Rachel" , "Green" , 100000 ); rachel.setEmployeeId(1000L); when(geekEmployeeService.findById(1000L)).thenReturn(Optional.of(rachel)); this .mockMvc.perform(get( "/geekemployees/1000" )) .andExpect(status().isOk()) .andExpect(content().string(containsString( "{\"employeeId\":1000,\"firstName\":\"Rachel\",\"lastName\":\"Green\",\"salary\":100000}" ))) .andDo(print()); } } |
Testcase Output: