Services
Learn
What is a service?​
A service is the part of your application that handles all the business logic. Services are, essentially, the meat of your applications. In Spring Boot, we use the @service
annotation to tell Spring that our class is a service.
The methods in your services are primarily called from the controller, but services in the same application are also free to communicate with one another (remember, in the RESTful API world, different application can only communicate with each other through their APIs). In this exercise, we will only be implementing one service, but there's no limit on the number of services you can include. Additionally, our service is going to be rather simple, but in practice, your services will be much larger and more complex.
What is Mockito and what do we use it for?​
Mockito is a mocking framework for our unit tests. Mockito is the process of faking or simulating classes. But why would we do that? Let's look at an example.
Say we wanted to write some tests for a controller to make sure that, from the perspective of a client application, our calls return the right information (we did this in Exercise 5). The client application doesn't care how the information is procure, only that it can make a call to an endpoint and get the right information back. The server-side application is a "black box". So, in our unit tests, we want to emulate that. We don't care how the information about our Students in gathered, but we know what it should look like. You will be adding a service to your application in this exercise that is responsible for retrieving the information about the Student
. Back for the purposes of testing the controller, we don't need to know how the service does this. This is where mocking comes into play.
We don't need our controller tests to make calls to an actual instance of our service class, so we simulate one instead. We use the @MockBean
annotation to denote an object reference as a mock. For instance, this line lets us pretend that we have an instance of StudentService
:
@MockBean
private StudentService studentService;
Now, let's look at an example unit test:
@Test
public void getStudentByIdTest() {
Integer id = 1;
String name = "John";
Student StudentToReturn = new Student(id, name);
Mockito.when(StudentService.getStudentById(id));
assertThat(Student.getId()).isEqualTo(id);
assertThat(Student.getName()).isEqualTo(name);
}
First, look at the first three lines of the method. Here, we're creating a Student
. This is the information we want to have at the end of test.
Now take a look at the highlighted line. This where the magic happens. Let's break it down:
- The
when()
method accepts a call to our mockedStudentService
and returns when that call is made. theReturn()
simple returns the given object when it's called.
This line reads like a sentence: "When this call is made, then return this object."
note
This technique is called stubbing, for those who are interested. We also use something called partial mocking, but the details aren't important here.
In short, this line simple returns StudentToReturn
when we call getStudentById(id)
in our mocked StudentService
. But why is that useful?
By mocking our StudentService
, we have removed the need to worry about how getStudentById()
works. getStudentById()
could be an extremely long and complicated method, ut as long as we know what is should return, we don't have to worry about changes in getStudentById()
impacting our tests. All the controller cares about is that it makes a call to getStudentById()
and get a Student
in return, so that's all we need to test. If a developer breaks getStudentById()
, the tests for the controller don't have to be rewritten.
In Summary, Mockito is a very useful, time-saving tool. It has a lot of functionality available-more than we can cover and more than we need in the bootcamp-but visit the Mockito JavaDocs to learn more.
Do​
1. Write StudentService
​
For now, we only need one method, getStudentById()
, that returns a Student
object.
In the src/main/java/com.geteche.students
directory, create a new package called service
.
In The newly created package, create a new Java class called StudentService.java
.
- Code Snippet
- Full Code
@Service
public class StudentService {
public Student getStudentById(Integer id) {
...
}
}
If copying the full file, you must change the package name (first line) to match your project name.
package com.geteche.students.service;
import org.springframework.stereotype.Service;
import com.geteche.students.model.Student;
@Service
public class StudentService {
public Student getStudentById(Integer id) {
return new Student(id, "John");
}
}
2. Update StudentController
​
We don't want our business logic in our controller-that violates the basic coding principle of separation of concerns-so we need to make call to our new service class.
Update the StudentController.java
class in the controller
package.
Add the dependency to the StudentService
using the @Autowired
annotation. Update the getStudentById()
method.
- Code Snippet
- Full Code
@Autowired
private StudentService studentService;
...
public Student getStudentById(@PathVariable("StudentId") Integer id) {
...
}
If copying the full file, you must change the package name (first line) to match your project name.
package com.geteche.students.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.geteche.students.model.Student;
import com.geteche.students.service.StudentService;
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping(value = "/v1/students/{studentId}", produces = MediaType.APPLICATION_JSON_VALUE)
public Student getStudentById(@PathVariable("studentId") Integer id) {
return studentService.getStudentById(id);
}
}
3. Write tests​
Write a new unit test for the getStudentById()
method we added in StudentService
. Additionally, update the unit test for StudentController
to utilize Mockito.
StudentServiceTest​
In the src/test/java/com.geteche.students
directory, create a new package called service
.
In the newly created package, create a new Java class called StudentServiceTest.java
.
- Code Snippet
- Full Code
@SpringBootTest
public class StudentServiceTest {
@Autowired
private StudentService studentService;
@Test
public void getStudentByIdTest() {
...
}
}
If copying the full file, you must change the package name (first line) to match your project name.
package com.geteche.students.service;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.geteche.students.model.Student;
@SpringBootTest
public class StudentServiceTest {
@Autowired
private StudentService studentService;
@Test
public void getStudentByIdTest() {
Integer id = 1;
Student Student = studentService.getStudentById(id);
assertThat(Student.getId()).isEqualTo(id);
assertThat(Student.getName()).isEqualTo("John");
}
}
StudentControllerTest​
In the src/test/java/com.geteche.students/controller
package, update StudentControllerTest.java
to mock the StudentService object.
- Code Snippet
- Full Code
...
@Autowired
private StudentController studentController;
@MockBean
private StudentService studentService;
@Test
public void getStudentByIdTest() {
...
}
}
If copying the full file, you must change the package name (first line) to match your project name.
package com.geteche.students.controller;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import com.geteche.students.model.Student;
import com.geteche.students.service.StudentService;
@SpringBootTest
public class StudentControllerTest {
@Autowired
private StudentController studentController;
@MockBean
private StudentService studentService;
@Test
public void getStudentByIdTest() {
Integer id = 1;
String name = "John";
Student studentToReturn = new Student(id, name);
Mockito.when(studentService.getStudentById(id)).thenReturn(studentToReturn);
Student Student = studentController.getStudentById(id);
assertThat(Student.getId()).isEqualTo(id);
assertThat(Student.getName()).isEqualTo(name);
}
}
Run your tests as in Controllers.
4. Deploy to nonprod​
Deploy the application to nonprod following the same steps as in Controllers.