Controller testing is special, so I'm worried. .. Make a note of how to write a test for the Controller of Spring Boot, which is full of business.
I will write a test for the following controller.
@Controller
@RequestMapping("/")
public class DemoController {
/**
* index
*/
@RequestMapping(path = "home", method = RequestMethod.GET)
public ModelAndView index(ModelAndView mav) {
//Set a value for message
mav.addObject("message", "hello world");
mav.setViewName("index");
return mav;
}
/**
*Input screen display
*/
@RequestMapping(path = "form", method = RequestMethod.GET)
public ModelAndView form(ModelAndView mav, Form form) {
//Set the initial value for name of form
form.setName("hoge");
mav.addObject("form", form);
mav.setViewName("demoForm");
return mav;
}
/**
*Receive the result and validation
*/
@RequestMapping(path = "form", method = RequestMethod.POST)
public ModelAndView formPost(ModelAndView mav, @Valid @ModelAttribute Form form,
BindingResult result) {
//Check validation
if (result.hasFieldErrors()) {
mav.addObject("errors", result.getFieldErrors());
mav.addObject("form", form);
mav.setViewName("demoForm");
return mav;
}
//Save form value
formService.saveData(form);
mav.setViewName("ok");
return mav;
}
}
In addition, the form class used for sending and receiving forms is as follows. The name is validated with the @NotBlank annotation.
Form.java
@Getter
@Setter
public class Form {
@NotBlank(message = "The name is a required item.")
private String name;
}
There are some promises in testing the Spring MVC Controller, so prepare them first. I will describe the detailed test in the following sections, but I am troubled by an unexpected error if this preparation is not done correctly. (I wasted hours on it.)
First of all, because it is necessary to run the DI function of Spring on Junit as well, Add @Runwith (..) and @SpringBootTest annotations to the test class.
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DemoControllerTest {
Register the class to be tested in the DI container with @Autowired. Prepare to reproduce the behavior of spring MVC with MockMvcBuilders.standaloneSetup (...). The @Before annotation is added because it is done before every @Test. After that, this mockMvc instance is used to generate a virtual request and execute a test.
private MockMvc mockMvc;
@Autowired
DemoController target;
@Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(target).build();
}
mockMVC was explained in detail on the here site.
The code so far is as follows.
DemoController
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DemoControllerTest {
private MockMvc mockMvc;
@Autowired
DemoController target;
@Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(target).build();
}
}
First, write a test to the home () method that draws the view by packing the message in the model with the GET method to / home. The items to be tested are as follows.
--Is the HTTP status code of the response correct? --Do you want to return the specified view? --Is the model packed with the correct variables?
Let's look at them in order.
Execute the request using the perform of mockMvc.
python
mockMvc.perform(Method name("Specified url"))
Then, test the response with the andExcept method.
python
.andExpect(Test items)
Since this time we are testing the HTTP status code, we will use status (). Status code 200 can be tested with status (). IsOk. A typical status code can be determined as follows.
status | Method |
---|---|
200 | status().isOk() |
308 | status().isPermanentRedirect() |
404 | status().isNotFound() |
403 | status().isForbidden() |
503 | status().isServiceUnavailable() |
The code so far is as follows.
DemoControllerTest.java
@Test
public void getIndexTest() throws Exception {
// when
mockMvc.perform(get("/home"))
.andExpect(status().isOk());
}
Check if "/ home" returns index.html. Use view (). name () to judge view.
.andExpect(view().name(Template name))
When added, it will be as follows.
DemoControllerTest.java
@Test
public void getIndexTest() throws Exception {
// when
mockMvc.perform(get("/home"))
.andExpect(status().isOk())
.andExpect(view().name("index"));
}
Next, test the state of the model to see if the variables used in the view are correctly packed in the model. Use model (). Attribute () to test if you are passing a variable to view
python
model().attribute(Variable name,value)
This time, the variable called message is filled with hello world, so It will be as follows.
python
@Test
public void getIndexTest() throws Exception {
// when
mockMvc.perform(get("/home"))
.andExpect(status().isOk())
.andExpect(view().name("index"))
.andExpect(model().attribute("message", "hello world"));
}
There may be other ways to do it, but once the index test is OK so far.
The form method packs the initialized formBean into a model and displays demoForm.html. However, if you just return the view of the form, it is the same as before, so test whether you can set the initial value in the name field of the form.
DemoController.java
form.setName("hoge");
mav.addObject("form", form);
The content of the object passed to the model can be determined by getting the return value of the request with mockMvc.perform (). AndReturn (). Receive the request result MvcResult with .andReturn, and get the view and model with getModelAndView from there, Furthermore, the model is acquired by getModel, and the value of "form" is acquired by the get method. Note that the return value of get () is of type object, so let's cast it with (Form). In summary, it looks like the following.
DemoControllerTest
@Test
public void getFormTest() throws Exception {
// when
MvcResult result = mockMvc.perform(get("/form"))
.andExpect(status().isOk())
.andExpect(view().name("demoForm"))
.andReturn();
//Get the value of the form packed in the model here
Form resultForm = (Form) result.getModelAndView().getModel().get("form");
// then
assertEquals(resultForm.getName(),"hoge");
}
Finally, test the formPost method. The formPost method receives the input value of form in the post request from demoform.html and receives it. If you do validation and there is no Erroll It calls FormService.saveData, saves the contents of the form, and calls ok.html. Since the process is complicated, write out the items to be tested.
Let's look at each one.
First, the test when there is a validationError. To do this, let's raise a validationError. Since the value of name is @NotBlank, an error will occur automatically if nothing is specified. Here, explicitly put an empty string in name. Use .param () or .flashAttr to enter values for the request parameters.
In the case of param,
// form.When putting hoge in name
// mockMvc.perform(post("url name").param(Parameter name,value))
mockMvc.perform(post("/form").param("name", "hoge"))
When using flashAttr,
// form.When putting hoge in name
// mockMvc.perform(post("url name").flashAttr(Parameter name,object))
Form form = new Form()
form.setName("hoge")
mockMvc.perform((post("/form")).flashAttr("form",form))
This time we will test using flashAttr. I'm testing if I'm getting a validation error and I'm viewing a demoForm view.
The fact that an error has occurred is ...
model().hasError()
It is judged by.
@Test
public void postFormTestInValid() throws Exception {
// given
Form form = new Form();
form.setName("");
// when
mockMvc.perform((post("/form")).flashAttr("form",form))
.andExpect(model().hasErrors())
.andExpect(model().attribute("form", form))
.andExpect(view().name("demoForm"));
}
Then test if no validationError occurs. The item I want to test here is
There are two. Whether to return the specified html is explained in the previous section, so Describes how to test if you are calling the specified method (formService.saveData).
The first thing to do is to mock the target service. In springMVC, by using @MockBean instead of @Mock, You can mock a class that is @Autowired.
Also, classes with the @MockBean annotation are automatically It is mocked when the @Autowired class (DemoController in this case) is executed in the test class. So let's add @MockBean before @Autowired DemoController target.
・
・
private MockMvc mockMvc;
//add to
@MockBean
FormService mockFormService;
@Autowired
private DemoController target;
・
・
Then, Mockit's verify method is used to determine the usage status of the Mocked object. The following tests that formService.saveData is called once with an instance called form as an argument.
// verify(Mock object name,Number of uses).Method name(argument);
verify(mockFormService, times(1)).saveData(form);
The entire test code is below. A value is set in form.name to prevent an error from occurring.
@Test
public void postFormTestValid() throws Exception {
// given
Form form = new Form();
form.setName("fuga");
// when
mockMvc.perform((post("/form")).flashAttr("form", form))
.andExpect(model().hasNoErrors())
.andExpect(model().attribute("form", form))
.andExpect(view().name("ok"));
// then
verify(mockFormService, times(1)).saveData(form);
}
The final code is as follows. Now the coverage of DemoController.java is 100% for both method and line.
DemoControllerTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DemoControllerTest {
private MockMvc mockMvc;
@MockBean
FormService mockFormService;
@Autowired
private DemoController target;
@Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(target).build();
}
@Test
public void getIndexTest() throws Exception {
// when
mockMvc.perform(get("/home"))
.andExpect(status().isOk())
.andExpect(view().name("index"))
.andExpect(model().attribute("message", "hello world"));
}
@Test
public void getFormTest() throws Exception {
// when
MvcResult result = mockMvc.perform(get("/form"))
.andExpect(status().isOk())
.andExpect(view().name("demoForm"))
.andReturn();
Form resultForm = (Form) result.getModelAndView().getModel().get("form");
// then
assertEquals(resultForm.getName(), "hoge");
}
@Test
public void postFormTestInValid() throws Exception {
// given
Form form = new Form();
form.setName("");
// when
mockMvc.perform((post("/form")).flashAttr("form", form))
.andExpect(model().hasErrors())
.andExpect(model().attribute("form", form))
.andExpect(view().name("demoForm"));
}
@Test
public void postFormTestValid() throws Exception {
// given
Form form = new Form();
form.setName("hoge");
// when
mockMvc.perform((post("/form")).flashAttr("form", form))
.andExpect(model().hasNoErrors())
.andExpect(model().attribute("form", form))
.andExpect(view().name("ok"));
// then
verify(mockFormService, times(1)).saveData(form);
}
}
I'm still not confident that this is correct, I hope it helps people who are just as worried about writing tests as they are.
https://qiita.com/NetPenguin/items/0e06779ecdd48d24a5db https://ito-u-oti.com/post-129/ http://blog.okazuki.jp/entry/2015/07/14/205627 https://terasolunaorg.github.io/guideline/5.4.1.RELEASE/ja/UnitTest/ImplementsOfUnitTest/UsageOfLibraryForTest.html
Recommended Posts