[JAVA] [SpringBoot] How to write a controller test

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.

Target Controller

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;

}

Preparation

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();
  }

}

Test to home method

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.

Is the HTTP status code of the response correct?

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());
  }

Do you want to return the specified view?

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"));
  }

Is the model packed with the correct variables?

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.

Test to formGet method

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);

Is the initial value ("hoge") set for the name of the 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");

  }

Testing the formPost method

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.

If there is a validation error

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"));
  }

If there is no validation error

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);
  }

result

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.

reference

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

[SpringBoot] How to write a controller test
How to write an RSpec controller test
How to write a unit test for Spring Boot 2
How to write a ternary operator
[RSpec] How to write test code
[Basic] How to write a Dockerfile Self-learning ②
[Introduction to Java] How to write a Java program
I want to write a unit test!
How to write Rails
How to write dockerfile
How to write docker-compose
How to write Mockito
How to write migrationfile
Rails: How to write a rake task nicely
[Rails] How to write when making a subquery
How to delete a controller etc. using a command
JUnit 5: How to write test cases in enum
How to write test code with Basic authentication
How to run the SpringBoot app as a service
How to write good code
Bit Tetris (how to write)
java: How to write a generic type list [Note]
How to write java comments
How to leave a comment
Great poor (how to write)
How to write a date comparison search in Rails
How to write Junit 5 organized
How to write Rails validation
How to write Rails seed
To write a user-oriented program (1)
[Ruby] How to write blocks
How to write Rails routing
How to write a core mod in Minecraft Forge 1.15.2
How to insert a video
How to create a method
How to test a class that handles application.properties with SpringBoot (request: pointed out)
How to dynamically write iterative test cases using test / unit (Test :: Unit)
Let's write how to make API with SpringBoot + Docker from 0
How to write a migration from Rails datetime type to date type
How to test a private method with RSpec for yourself
How to unit test Spring AOP
How to add columns to a table
Studying Java # 6 (How to write blocks)
[Rails] How to write in Japanese
How to make a Java container
How to sign a Minecraft MOD
How to make a JDBC driver
[Java] How to create a folder
Rails on Tiles (how to write)
[Rails] How to write exception handling?
[Swift] How to send a notification
How to write Java variable declaration
How to make a splash screen
How to make a Jenkins plugin
How to make a Maven project
How to write easy-to-understand code [Summary 3]
How to make a Java array
[RSpec on Rails] How to write test code for beginners by beginners
[Reading impression] "How to learn Rails, how to write a book, and how to teach"
How to execute a contract using web3j
How to sort a List using Comparator