Testing: mocking

This commit is contained in:
djmil 2023-07-26 16:10:33 +02:00
parent 45d0361548
commit 0ddd30a245
6 changed files with 150 additions and 30 deletions

View File

@ -270,3 +270,89 @@ public class WebLayerTest {
``` ```
The test assertion is the same as in the previous case. However, in this test, Spring Boot instantiates only the web layer rather than the whole context. In an application with multiple controllers, you can even ask for only one to be instantiated by using, for example, `@WebMvcTest(HomeController.class)`. The test assertion is the same as in the previous case. However, in this test, Spring Boot instantiates only the web layer rather than the whole context. In an application with multiple controllers, you can even ask for only one to be instantiated by using, for example, `@WebMvcTest(HomeController.class)`.
## Testing with mocks
So far, our `GreetingController` is simple and has no dependencies. We could make it more realistic by introducing an extra component, say another service which will harbor some business logic, in our case - calculate length of a URL `name` parameter.
`src/main/java/djmil/hellomvc/GreetingService.java`
```java
package djmil.hellomvc;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
@Service
public class GreetingService {
public int nameLength(String name) {
return name.length();
}
}
```
In SpringBoot [`@Service`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Service.html) annotation is usually used to mark business logic classes. Doing so allows SpringBoot IoC to handle dependencies between different classes. Here, we have to update `GreetingsControler` to use `GrretingsSrvice` to handle `/greeting` request:
```java
@Controller
public class GreetingController {
private final GreetingService greetigService;
public GreetingController(GreetingService greetigService) {
this.greetigService = greetigService;
}
@GetMapping("/greeting")
public String greeting(@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) {
model.addAttribute("name", name);
model.addAttribute("nameLength", greetigService.nameLength(name));
// NOTE: Here we can request some data from our RESTful backend as well
return "greeting"; // view tempalte
}
}
```
Spring IoC will insert instance of `GreetingService` as a constructor parameter of `GreetingController`, which we store as a private class member for later use.
Also, to make full use of newly created service, update `src\main\resources\templates\greeting.html`:
```html
<p th:text="'Hello, ' + ${name} + '!'" />
<p th:text="'Length of a given name is ' + ${nameLength} + ' characters.'" />
```
Run the tests, and notice that only `GreetingControllerTest` has failed.
### Mocking
To fix the test, we have to provide mock implementation of a `GreetingService` class inside `GreetingControllerTest`:
```java
import static org.mockito.Mockito.when;
@WebMvcTest(GreetingController.class)
public class CreetingControllerTests {
@Autowired
private MockMvc mockMvc;
@MockBean
private GreetingService service;
@Test
public void greetingShouldReturnMessageFromService() throws Exception {
when(service.nameLength("World")).thenReturn(7);
this.mockMvc.perform(get("/greeting"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("Length of a given name is 7 characters.")));
}
}
```
Notice the `@MockBean` annotation, which is essentially used to obtain an interface of a mocked class. Later, `when` function used to define an actual mock.

View File

@ -8,13 +8,20 @@ import org.springframework.web.bind.annotation.RequestParam;
@Controller @Controller
public class GreetingController { public class GreetingController {
private final GreetingService greetigService;
public GreetingController(GreetingService greetigService) {
this.greetigService = greetigService;
}
@GetMapping("/greeting") @GetMapping("/greeting")
public String greeting(@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) { public String greeting(@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) {
// NOTE: Here we can request some data from our RESTful backend as well
model.addAttribute("name", name); model.addAttribute("name", name);
model.addAttribute("nameLength", greetigService.nameLength(name));
// NOTE: Here we can request some data from our RESTful backend as well
return "greeting"; // <<-- template return "greeting"; // view tempalte
} }
} }

View File

@ -0,0 +1,12 @@
package djmil.hellomvc;
import org.springframework.stereotype.Service;
@Service
public class GreetingService {
public int nameLength(String name) {
return name.length();
}
}

View File

@ -6,6 +6,7 @@
</head> </head>
<body> <body>
<p th:text="'Hello, ' + ${name} + '!'" /> <p th:text="'Hello, ' + ${name} + '!'" />
<p th:text="'Length of a given name is ' + ${nameLength} + ' characters.'" />
<input placeholder="Enter username.."/> <input placeholder="Enter username.."/>
<button>Go!</button> <button>Go!</button>
</body> </body>

View File

@ -0,0 +1,41 @@
package djmil.hellomvc;
import static org.hamcrest.Matchers.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 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;
@WebMvcTest(GreetingController.class)
public class CreetingControllerTests {
@Autowired
private MockMvc mockMvc;
@MockBean
private GreetingService service;
// @Test
// public void shouldReturnDefaultMessage() throws Exception {
// this.mockMvc.perform(get("/greeting"))
// .andDo(print())
// .andExpect(status().isOk())
// .andExpect(content().string(containsString("Hello, World")));
// }
@Test
public void greetingShouldReturnMessageFromService() throws Exception {
when(service.nameLength("World")).thenReturn(7);
this.mockMvc.perform(get("/greeting"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("Length of a given name is 7 characters.")));
}
}

View File

@ -1,27 +0,0 @@
package djmil.hellomvc;
import static org.hamcrest.Matchers.containsString;
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 org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest
public class WebLayerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void shouldReturnDefaultMessage() throws Exception {
this.mockMvc.perform(get("/greeting"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello, World")));
}
}