Writing a Unit Test using Spring Boot (Part 2)

Yiğitcan Nalcı
Javarevisited
Published in
5 min readJun 16, 2019

--

After I described “How to develop a REST service with Spring Boot and MongoDB”, I decided to explain how to write a unit test using Spring Boot.

udemy

In this tutorial, unit tests were written in the project developed in the previous story, so the technologies and settings used in the previous project are the same.

You can download the project source code from this GitHub link.

github

Spring Boot simplifies unit testing with SpringRunner. It is also easier to write unit tests for REST Controller with MockMVC. For writing a unit test, Spring Boot Starter Test dependency should be added to your build configuration file (pom.xml for our project).

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

After doing that, we’re ready to write our first unit test.

Test for StudentService.java

In this section, we will implement a unit test for StudentService.java shown belove.

@Service
public class StudentServiceImpl implements StudentService {

@Autowired
private StudentRepository studentRepository;

@Override
public List<Student> findAll() {
return studentRepository.findAll();
}

@Override
public Student findByStudentNumber(long studentNumber) {
return studentRepository.findByStudentNumber(studentNumber);
}

@Override
public Student findByEmail(String email) {
return studentRepository.findByEmail(email);
}

@Override
public List<Student> findAllByOrderByGpaDesc() {
return studentRepository.findAllByOrderByGpaDesc();
}

@Override
public Student saveOrUpdateStudent(Student student) {
return studentRepository.save(student);
}

@Override
public void deleteStudentById(String id) {
studentRepository.deleteById(id);
}
}

First, we need to write @RunWith(SpringRunner.class) annotation above the class. This annotation uses SpringJUnit4ClassRunner to run unit tests.

Second, @SpringBootTest annotation is added. This annotation provides many features for test classes. For more information click here.

Then, the class (StudentService.java) to be tested and its dependencies are added as shown below.

@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentServiceTest {

@Autowired
private StudentService studentService;

@MockBean
private StudentRepository studentRepository;
...}

Now, we can write our mock objects to test StudentService.java. Ragcrix and yigit are created. The following values are assigned to the objects.

private Student ragcrix;
private Student yigit;

private final Long ragcrixStudentNumber = 23L;
private final Long yigitStudentNumber = 91L;
private final String ragcrixEmail = "ragcrix@rg.com";
private final String yigitEmail = "yigit@ygt.com";
private final List<Student> students = new ArrayList<>();

@Before
public void setup() {
ragcrix = new Student();
ragcrix.setId("aBc123");
ragcrix.setName("ragcrix");
ragcrix.setEmail(ragcrixEmail);
ragcrix.setStudentNumber(ragcrixStudentNumber);
ragcrix.setCourseList(Arrays.asList("Math", "Science"));
ragcrix.setGpa(3.37f);

yigit = new Student();
yigit.setId("dEf345");
yigit.setName("yigit");
yigit.setEmail(yigitEmail);
yigit.setStudentNumber(yigitStudentNumber);
yigit.setCourseList(Arrays.asList("Turkish", "German"));
yigit.setGpa(3.58f);

students.add(ragcrix);
students.add(yigit);

For findAll() method in StudentService, we need to mock findAll() method in StudentRepository.java. After that, we are ready to write test method. For this,testFindAll_thenStudentListShouldBeReturned() method is written. This method simply checks the size of the returned data.

...Mockito.when(studentRepository.findAll()).thenReturn(students);...@Test
public void testFindAll_thenStudentListShouldBeReturned() {
List<Student> foundStudents = studentService.findAll();

assertNotNull(foundStudents);
assertEquals(2, foundStudents.size());
}

For findByStudentNumber() method in StudentService, we need to mock findByStudentNumber() method in StudentRepository.java. After that, we are ready to write test method. For this,testFindByStudentNumber23_thenRagcrixShouldBeReturned() method is written.

...Mockito.when(studentRepository.findByStudentNumber
(ragcrixStudentNumber)).thenReturn(ragcrix);
...@Test
public void testFindByStudentNumber23_thenRagcrixShouldBeReturned() {
Student found = studentService.findByStudentNumber(ragcrixStudentNumber);

assertNotNull(found);
assertEquals(ragcrix.getName(), found.getName());
assertEquals(ragcrix.getId(), found.getId());
}

For findByEmail(), findAllByOrderByGpaDesc(), saveOrUpdateStudent(), deleteStudentById() methods in StudentService, related methods in StudentRepository.java are mocked same way. Then, tests methods for StudentService are written as shown below.


...
Mockito.when(studentRepository.findByEmail(yigitEmail))
.thenReturn(yigit);

Mockito.when(studentRepository.findAllByOrderByGpaDesc())
.thenReturn(students.stream().sorted(
Comparator.comparing(Student::getGpa).reversed()).collect(Collectors.toList()));

Mockito.when(studentRepository.save(ragcrix)).thenReturn(ragcrix);
...@Test
public void testFindByEmail_thenYigitShouldBeReturned() {
Student found = studentService.findByEmail(yigitEmail);

assertNotNull(found);
assertEquals(yigit.getName(), found.getName());
assertEquals(yigit.getId(), found.getId());
}

@Test
public void testFindAllByOrderByGpaDesc_thenStudentsShouldBeReturned_byGPADesc() {
List<Student> foundStudents = studentService.findAllByOrderByGpaDesc();

assertNotNull(foundStudents);
assertEquals(2, foundStudents.size());
assertEquals(yigit.getName(), foundStudents.get(0).getName());
assertEquals(yigit.getId(), foundStudents.get(0).getId());
}

@Test
public void testSaveOrUpdateStudent_thenStudentShouldBeReturned() {
Student found = studentService.saveOrUpdateStudent(ragcrix);

assertNotNull(found);
assertEquals(ragcrix.getName(), found.getName());
assertEquals(ragcrix.getId(), found.getId());
}

@Test
public void testDeleteStudentById() {
studentService.deleteStudentById(ragcrix.getId());

Mockito.verify(studentRepository, Mockito.times(1))
.deleteById(ragcrix.getId());
}
Test results for StudentServiceTest.java

Test for StudentRestController.java

Spring Boot provides an easy way to write a unit test for Rest controller. With the help of SpringJUnit4ClassRunner and MockMVC, a web application context can be created to write unit test for Rest controller.

codeflex

First, we add the necessary annotations to our test class as in the previous test. MockMvc is a class of Spring Boot testing tools that allows you to test controllers without having to start an HTTP server. It is easier to test web controller with @WebMvcTest. Click for details.

@RunWith(SpringRunner.class)
@WebMvcTest
public class StudentRestControllerTest {

@Autowired
private MockMvc mvc;

@MockBean
private StudentService studentService;

private ObjectMapper objectMapper = new ObjectMapper();

After arrange configurations for web controller test, we can add mock objects to this class like below.

private Student ragcrix;
private Student yigit;

private final Long ragcrixStudentNumber = 23L;
private final Long yigitStudentNumber = 91L;

@Before
public void setup() {
ragcrix = new Student();
ragcrix.setId("aBc123");
ragcrix.setName("ragcrix");
ragcrix.setEmail("ragcrix@rg.com");
ragcrix.setStudentNumber(ragcrixStudentNumber);
ragcrix.setCourseList(Arrays.asList("Math", "Science"));
ragcrix.setGpa(3.37f);

yigit = new Student();
yigit.setId("dEf345");
yigit.setName("yigit");
yigit.setEmail("yigit@ygt.com");
yigit.setStudentNumber(yigitStudentNumber);
yigit.setCourseList(Arrays.asList("Turkish", "German"));
yigit.setGpa(3.58f);
}

Now, we are ready for writing first test method. ForgetAllStudents() in StudentRestController.java, the following method is written. mvc.perform() method is used for testing endpoints. You can compare expected and actual many data with this method like below.

In this test method, whenstudentService.findAll() is called, a list of students will be returned. After that, getAllStudents() in controller is being tested with mvc.perform().

@Test
public void givenStudents_whenGetAllStudents_thenReturnJsonArray() throws Exception {
given(studentService.findAll()).willReturn(Arrays.asList(ragcrix));

mvc.perform(get("/students/")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].name", is(ragcrix.getName())));
}

As you see below, tests of other GET methods is written like givenStudents_whenGetAllStudents_thenReturnJsonArray() method.

@Test
public void givenStudent_whenFindByStudentNumber_thenReturnJsonArray() throws Exception {
given(studentService.findByStudentNumber(ragcrixStudentNumber)).willReturn(ragcrix);

mvc.perform(get("/students/byStudentNumber/{studentNumber}", ragcrixStudentNumber)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is(ragcrix.getName())));
}

@Test
public void givenStudent_whenFindAllByOrderByGpaDesc_thenReturnJsonArray() throws Exception {
given(studentService.findAllByOrderByGpaDesc()).willReturn(Arrays.asList(yigit, ragcrix));

mvc.perform(get("/students/orderByGpa/")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].name", is(yigit.getName())));
}

After the tests of GET methods are completed, we can write the POST method test. For saveStudent() method in controller, saveStudent_itShouldReturnStatusOk() method is written. In this method, studentService.saveOrUpdateStudent() method is mocked first. The Student object (yigit) to be saved is converted to jsonString with the objectMapper. After that /save endpoint is tested using mvc.perform() method.

As in other GET methods written, you can also test returned value of /save endpoint.

@Test
public void saveStudent_itShouldReturnStatusOk() throws Exception {
given(studentService.saveOrUpdateStudent(any(Student.class))).willReturn(yigit);

String jsonString = objectMapper.writeValueAsString(yigit);

mvc.perform(post("/students/save/")
.contentType(MediaType.APPLICATION_JSON).content(jsonString))
.andExpect(status().isOk());
}

Our last method in StudenRestController.java is deleteStudentByStudentNumber(). For this method, deleteStudentByStudentNumber_itShouldReturnStatusOk() is written. First, the related method in StudentService.java is mocked like other test methods. After that with mvc.perform(), the /delete/{studentNumber} endpoint is tested.

@Test
public void deleteStudentByStudentNumber_itShouldReturnStatusOk() throws Exception {
given(studentService.findByStudentNumber(ragcrixStudentNumber)).willReturn(ragcrix);
Mockito.doNothing().when(studentService).deleteStudentById(any(String.class));

mvc.perform(delete("/students/delete/{studentNumber}", ragcrixStudentNumber)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
Test results for StudentRestControllerTest.java

Thank you for reading! 🙏 Your thoughts are very valuable to me. Please feel free to share. 😄

--

--

Yiğitcan Nalcı
Javarevisited

Dad. Software Engineer. Sports lover, doer. Curious about science and technology. Novice writer for now. https://www.linkedin.com/in/yigitcannalci