Springboot unit test

Time:2021-3-31

See a very good article about spring boot unit testing, here to turn, original address: spring boot dry goods series: (12) spring boot unit testing

1、 Preface

This time, I will introduce the integrated use of unit testing in spring boot. This article will introduce the following four points to basically meet the daily needs

  • Unit test of service layer
  • Unit test of controller layer
  • New assertion assertthat uses
  • Unit test rollback

It’s very easy to introduce unit testing into spring boot, depending on the following:

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

You can write unit tests in the generated test class. Use the spring boot test test tool class of spring. The spring boot starter test starter can introduce these spring boot test modules

  • JUnit: Java application unit test standard class library.
  • Spring test & spring boot test: spring boot application function integration test support.
  • Mockito: a Java mock testing framework.
  • Assert J: a lightweight assertion class library.
  • Hamcrest: an object matcher class library.
  • Jsonassert: an assertion library for JSON.
  • Jsonpath: a JSON operation class library.

Springboot unit test

2、 Service unit test

The unit test classes in spring boot are written in the Src / test / Java directory. You can create specific test classes manually. If it’s idea, you can create test classes automatically through idea, as shown in the figure below. You can also use shortcut keys⇧⌘T(MAC) orCtrl+Shift+T(window), as follows:

Springboot unit test

Springboot unit test

The automatic generation of test classes is as follows:
Springboot unit test

Then write the created test class, and the specific code is as follows:

package com.dudu.service;
import com.dudu.domain.LearnResource;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.hamcrest.CoreMatchers.*;

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

    @Autowired
    private LearnService learnService;
    
    @Test
    public void getLearn(){
        LearnResource learnResource=learnService.selectByKey(1001L);
        Assert.assertThat ( learnResource.getAuthor (), is ("Dudu MD independent blog");
    }
}

The above is the simplest way to write unit tests, with only one unit at the top@RunWith(SpringRunner.class)andSpringBootTestWhen you want to execute, put the mouse on the corresponding method and right-click to select run.

Runwith explains

@Runwith is a runner, such as @ runwith (spring junit4) ClassRunner.class ), let the test run in the spring test environment, @ runwith (springjunit4) ClassRunner.class )Spring junit4classrunner is used to automatically create the application context of spring at the beginning of the test. If you want to create a spring container, you have to create a spring container web.xml Configure classloder. If you annotate @ runwith, you can use the spring container directly. If you annotate @ test, you don’t need to start the spring container

I used the assert that assertion in the test case, which will be introduced in the following, and I recommend you to use it.

3、 Controller unit test

The above is only for testing the service layer, but sometimes you need to test the controller layer (API). At this time, you need to use mockmvc. You can test these interfaces without starting the project.

Mockmvc realizes the simulation of HTTP request, which can directly use the form of network to switch to the call of controller. This can make the test fast, independent of the network environment, and provides a set of verification tools, which can make the request verification unified and convenient.

Controller class:

package com.dudu.controller;

/**Tutorial page
 * Created by tengj on 2017/3/13.
 */
@Controller
@RequestMapping("/learn")
public class LearnController  extends AbstractController{
    @Autowired
    private LearnService learnService;
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @RequestMapping("")
    public String learn(Model model){
        model.addAttribute("ctx", getContextPath()+"/");
        return "learn-resource";
    }

    /**
     *Tutorial list query
     * @param page
     * @return
     */
    @RequestMapping(value = "/queryLeanList",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject queryLearnList(Page<LeanQueryLeanListReq> page){
        List<LearnResource> learnList=learnService.queryLearnResouceList(page);
        PageInfo<LearnResource> pageInfo =new PageInfo<LearnResource>(learnList);
        return AjaxObject.ok().put("page", pageInfo);
    }
    
    /**
     *New tutorial
     * @param learn
     */
    @RequestMapping(value = "/add",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject addLearn(@RequestBody LearnResource learn){
        learnService.save(learn);
        return AjaxObject.ok();
    }

    /**
     *Modify the tutorial
     * @param learn
     */
    @RequestMapping(value = "/update",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject updateLearn(@RequestBody LearnResource learn){
        learnService.updateNotNull(learn);
        return AjaxObject.ok();
    }

    /**
     *Delete tutorial
     * @param ids
     */
    @RequestMapping(value="/delete",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject deleteLearn(@RequestBody Long[] ids){
        learnService.deleteBatch(ids);
        return AjaxObject.ok();
    }

    /**
     *Get a tutorial
     * @param id
     */
    @RequestMapping(value="/resource/{id}",method = RequestMethod.GET)
    @ResponseBody
    public LearnResource qryLearn(@PathVariable(value = "id") Long id){
       LearnResource lean= learnService.selectByKey(id);
        return lean;
    }
}

Here we also create a controller test class automatically. The specific code is as follows:

package com.dudu.controller;

import com.dudu.domain.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest

public class LearnControllerTest {
    @Autowired
    private WebApplicationContext wac;

    private MockMvc mvc;
    private MockHttpSession session;


    @Before
    public void setupMockMvc(){
        mvc =  MockMvcBuilders.webAppContextSetup (WAC). Build(); // initialize mockmvc object
        session = new MockHttpSession();
        User user =new User("root","root");
        session.setAttribute ("user", user); // the interceptor will determine whether the user logs in, so a user is injected here
    }

    /**
     *New tutorial test case
     * @throws Exception
     */
    @Test
    public void addLearn() throws Exception{
        String json="{\"author\":\"HAHAHAA\",\"title\":\"Spring\",\"url\":\"http://tengj.top/\"}";
        mvc.perform(MockMvcRequestBuilders.post("/learn/add")
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .content( json.getBytes ()) // pass the JSON parameter
                    .session(session)
            )
           .andExpect(MockMvcResultMatchers.status().isOk())
           .andDo(MockMvcResultHandlers.print());
    }

    /**
     *Get tutorial test cases
     * @throws Exception
     */
    @Test
    public void qryLearn() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .session(session)
            )
           .andExpect(MockMvcResultMatchers.status().isOk())
           .andExpect(M ockMvcResultMatchers.jsonPath ("$. Author"). Value ("Dudu MD independent blog"))
           .andExpect(M ockMvcResultMatchers.jsonPath ("$. Title"). Value ("spring boot dry series"))
           .andDo(MockMvcResultHandlers.print());
    }

    /**
     *Modify tutorial test cases
     * @throws Exception
     */
    @Test
    public void updateLearn() throws Exception{
        String JSON = {\ "author \": \ "test modification \", \ "ID \": 1031, \ "title \": \ "spring boot dry series \", \ "URL \": \ " http://tengj.top/ \"}";
        mvc.perform(MockMvcRequestBuilders.post("/learn/update")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content( json.getBytes ()) // pass the JSON parameter
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

    /**
     *Delete tutorial test case
     * @throws Exception
     */
    @Test
    public void deleteLearn() throws Exception{
        String json="[1031]";
        mvc.perform(MockMvcRequestBuilders.post("/learn/delete")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content( json.getBytes ()) // pass the JSON parameter
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

}

The above implements the basic test cases of adding, deleting, modifying and querying. When using mockmvc, you need to use mockmvcbuilders to build mockmvc objects, as follows

@Before
public void setupMockMvc(){
    mvc =  MockMvcBuilders.webAppContextSetup (WAC). Build(); // initialize mockmvc object
    session = new MockHttpSession();
    User user =new User("root","root");
    session.setAttribute ("user", user); // the interceptor will determine whether the user logs in, so a user is injected here
}

Because the interceptor will determine whether to log in or not, I injected a user here. You can also directly modify the interceptor to cancel verifying the user’s login, and then open it after testing.

Here is an example to introduce the simple method of mockmvc

/**
 *Get tutorial test cases
 * @throws Exception
 */
@Test
public void qryLearn() throws Exception {
    mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .session(session)
        )
       .andExpect(MockMvcResultMatchers.status().isOk())
       .andExpect(M ockMvcResultMatchers.jsonPath ("$. Author"). Value ("Dudu MD independent blog"))
       .andExpect(M ockMvcResultMatchers.jsonPath ("$. Title"). Value ("spring boot dry series"))
       .andDo(MockMvcResultHandlers.print());
}
  1. mockMvc.perform Execute a request
  2. Mo ckMvcRequestBuilders.get (“/ user / 1”) constructs a request, and the post request uses the. Post method
  3. contentType( MediaType.APPLICATION_ JSON_ Utf8) represents the format of the data sent by the senderapplication/json;charset=UTF-8
  4. accept( MediaType.APPLICATION_ JSON_ Utf8) represents the data type that the client wants to acceptapplication/json;charset=UTF-8
  5. Session (session) injects a session so that the interceptor can pass through
  6. ResultActions.andExpect Add assertion after execution
  7. ResultActions.andExpect (M ockMvcResultMatchers.status (). Isok ()) method to see whether the status response code of the request is 200. If not, an exception is thrown and the test fails
  8. andExpect(M ockMvcResultMatchers.jsonPath (“$. Author”). Value (“Dudu MD independent blog”)) here, jsonpath is used to obtain whether the author field comparison isDudu MD independent blogIt doesn’t mean the test doesn’t pass
  9. ResultActions.andDo Add a result processor to indicate that you want to do something about the result, which is better than using M ockMvcResultHandlers.print () output the whole response result information

This example tests as follows:
Springboot unit test

4、 New assertion assertthat uses

JUnit 4.4 combined with hamcrest provides a new assertion syntax — assert that. Programmers can use only one assertion statement of assert that, combined with the match provided by hamcrest, to express all the testing ideas. The version we introduced is junit4.12, so we support assert that.

Listing 1 basic syntax of assertthat

assertThat( \[value\], \[matcher statement\] );  
  • Value is the variable value that you want to test next;
  • A match statement is a declaration of the expected value of the preceding variable expressed by a hamcrest match. If the value matches the expected value expressed by the match statement, the test succeeds, otherwise the test fails.

Advantages of assertthat

  • Advantage 1: in the past, JUnit provided many assertion statements, such as: assert equals, assert notsame, assert false, assert true, assert notnull, assert null, and so on. Now with JUnit 4.4, an assert that can replace all assertions In this way, only one assertion method can be used in all unit tests, which makes the writing of test cases easier, the code style more unified, and the test code easier to maintain.
  • Advantage 2: assertthat uses the matcher of hamcrest. Users can use the matching criteria specified by the matcher to precisely specify the conditions they want to meet. It has strong readability and is more flexible to use. As shown in Listing 2:

Listing 2 is a comparison between using matcher and not using it

//I want to determine whether a string s contains one of the substrings "developer" or "works"
//Versions before JUnit 4.4: asserttrue (s.indexof ("developer") > - 1 | s.indexof ("works") > - 1);
// JUnit 4.4:
assertThat(s, anyOf(containsString("developer"), containsString("Works"))); 
//The match character anyof indicates that it is true if any condition is satisfied, similar to logic or "|", and the match character containsstring indicates whether there is a parameter 
//String, the article will be the next match for a specific introduction

Advantage 3: assertthat is no longer like assertequals, which uses the more difficult “predicate object subject” grammatical pattern (such as: assertequals (3, x);), on the contrary, assertthat uses an easy to read grammatical pattern similar to “subject predicate object” (such as: assertthat (x, is (3));), which makes the code more intuitive and easy to read.


Note: this blog post is reprinted, original address: spring boot dry goods series: (12) spring boot unit test