SpringBoot unit tests are used with Mockito

Unit tests should follow →The principle of AIR

SpringBoot test support is provided by two modules:

  • Spring-boot-test contains the core project
  • Spring-boot-test-autoconfigure supports automatic configuration of tests

Usually we just need to introduce the spring-boot-starter-test dependency, which contains some common modules Junit, Spring Test, AssertJ, Hamcrest, Mockito, etc.

Related note

SpringBoot uses Junit4 as a unit testing framework, so the annotations are consistent with Junit4.

annotations role
@test (excepted==xx.class,timeout= milliseconds) To qualify a method as a test method, the ExcepTED argument ignores some exception classes
@Before Execute each test method once before it is run
@BeforeClass Execute before all test methods execute
@After Execute once after each test method is run
@AfterClass Execute after all test methods have executed
@Ignore Modified classes or methods are ignored by the test runner
@RunWith Change the test runner

@SpringBootTest

SpringBoot provides an @SpringBooTtest annotation for testing SpringBoot applications, which can be used as an alternative to the standard Spring-test-@ContextConfiguration annotation, The principle is to create the ApplicationContext in the test through the SpringApplication.

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTest {}Copy the code

This annotation provides two properties for configuration:

  • webEnvironment: Specifies the Web application environment, which can be the following values
    • MOCK: Provides a MOCK Servlet environment where the built-in Servlet container is not started and can be used in conjunction with @AutoConfiguRemockMVC for mockMV-based application testing.
    • RANDOM_PORT: loading a EmbeddedWebApplicationContext and provide a truly embedded Servlet environment, random port.
    • DEFINED_PORT: loading a EmbeddedWebApplicationContext and provide a truly embedded Servlet environment, the default port 8080 or designated by the configuration file.
    • NONE: Loads the ApplicationContext with SpringApplication, but does not provide any servlet environment.
  • Classes: Specifies the application startup class. Normally, no setting is required because SpringBoot will automatically search until the @SpringBootApplication or @SpringBootConfiguration annotation is found.

Unit test rollback

If you add the @Transactional annotation, it will roll back at the end of each test method.

But if you use a true Servlet environment like RANDOM_PORT or DEFINED_PORT, the HTTP client and server will run in different threads, separating transactions. In this case, any transactions started on the server are not rolled back.

assertions

JUnit4 provides a new assertion syntax, assertThat, in conjunction with the matching characters provided by Hamcrest, to express the whole idea of testing.

// General card
int s = new C().add(1.1);
// allOf: All conditions must be true for the test to pass
assertThat(s, allOf(greaterThan(1), lessThan(3)));
// anyOf: As long as one condition is true, the test passes
assertThat(s, anyOf(greaterThan(1), lessThan(1)));
// anything: Pass the test, no matter what
assertThat(s, anything());
// is: The test passes if the variable is equal to the specified value
assertThat(s, is(2));
// not: In contrast to is, the test passes if the value of the variable is not equal to the specified value
assertThat(s, not(1));

// The value is matched
double d = new C().div(10.3);
// closeTo: Float variable values within 3.0±0.5, test pass
assertThat(d, closeTo(3.0.0.5));
// greaterThan: the test passes if the value of the variable is greaterThan the specified value
assertThat(d, greaterThan(3.0));
// lessThan: If the value of a variable is smaller than the specified value, the test passes
assertThat(d, lessThan(3.5));
// greaterThanOrEuqalTo: The test passes if the value of the variable is greater than or equal to the specified value
assertThat(d, greaterThanOrEqualTo(3.3));
// lessThanOrEqualTo: if the value of a variable is lessThanOrEqualTo the specified value, the test passes
assertThat(d, lessThanOrEqualTo(3.4));

// String matching characters
String n = new C().getName("Magci");
// containsString: The test passes if the string variable contains the specified string
assertThat(n, containsString("ci"));
// startsWith: The test passes if the string variable begins with the specified string
assertThat(n, startsWith("Ma"));
// endsWith: the test passes if the string variable endsWith the specified string
assertThat(n, endsWith("i"));
// euqalTo: The test passes when the string variable is equal to the specified string
assertThat(n, equalTo("Magci"));
// equalToIgnoringCase: The test passes when a string variable equals the specified string, regardless of case
assertThat(n, equalToIgnoringCase("magci"));
/ / equalToIgnoringWhiteSpace: string variable in the case of ignoring end any Spaces is equal to the specified string when the test pass
assertThat(n, equalToIgnoringWhiteSpace(" Magci "));

// set the card
List<String> l = new C().getList("Magci");
// hasItem: The test passes if the Iterable contains the specified element
assertThat(l, hasItem("Magci"));

Map<String, String> m = new C().getMap("mgc"."Magci");
// hasEntry: The test passes if the Map variable contains the specified key-value pairs
assertThat(m, hasEntry("mgc"."Magci"));
// hasKey: The test passes if the Map variable contains the specified key
assertThat(m, hasKey("mgc"));
// hasValue: The test passes if the Map variable contains the specified value
assertThat(m, hasValue("Magci"));
Copy the code

Basic unit test examples

Here is a basic unit test example that asserts the return result of a method:

@Service
public class UserService {

    public String getName(a) {
        return "lyTongXue"; }}Copy the code
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService service;

    @Test
    public void getName(a) {
        String name = service.getName();
        assertThat(name,is("lyTongXue")); }}Copy the code

The Controller test

Spring provides MockMVC to support RESTful Spring MVC testing, using MockMvcBuilder to construct MockMVC instances. MockMvc has two implementations:

  • StandaloneMockMvcBuilder: Specifies the WebApplicationContext, from which it will get the corresponding controller and the corresponding MockMvc

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UserControllerTest  {
        @Autowired
        private WebApplicationContext webApplicationContext;
        private MockMvc mockMvc;
        @Before
        public void setUp(a) throws Exception {
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    } 
    Copy the code
  • DefaultMockMvcBuilder: Specify a set of controllers with parameters that do not need to be retrieved from the context

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UserControllerTest  {
        private MockMvc mockMvc;
        @Before
        public void setUp(a) throws Exception {
            mockMvc = MockMvcBuilders.standaloneSetup(newUserController()).build(); }}Copy the code

Here is a simple use case to test the /v1/users/{ID} interface of the UserController.

@RestController
@RequestMapping("v1/users")
public class UserController {

    @GetMapping("/{id}")
    public User get(@PathVariable("id") String id) {
        return new User(1."lyTongXue");
    }

    @Data
    @AllArgsConstructor
    public class User {
        private Integer id;
        privateString name; }}Copy the code
// ...
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

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

    @Autowired
    private WebApplicationContext webApplicationContext;
    private MockMvc mockMvc;

    @Before
    public void setUp(a) {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void getUser(a) {
        mockMvc.perform(get("/v1/users/1")
                .accept(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
           .andExpect(content().string(containsString("\"name\":\"lyTongXue\""))); }}Copy the code

Methods described

  • Perform: Performs a RequestBuilder request that returns a ResultActions instance object that performs expectations and other actions on the result of the request

  • Get: Declares a method for sending a GET request. See the →MockMvcRequestBuilders documentation for more request types

  • AndExpect: Add ResultMatcher verification rules to verify whether the request results are correct. The verification rules can be referred to →MockMvcResultMatchers document

  • AndDo: Add ResultHandler result handlers, such as printing results to the console during debugging. See the documentation →MockMvcResultHandlers for more handlers

  • AndReturn: Returns the result of executing the request, which is an MvcResult instance object →MvcResult document

The Mock data

In unit testing, calls to the Service layer often involve external dependencies on databases, middleware, and so on. In the AIR principle of unit testing, unit tests should be repeatable and should not be influenced by the external environment. At this point we can Mock out an implementation to handle this situation.

If static methods, private methods and other special validation tests are not needed, only the Mockito of Spring Boot can be used to complete relevant test data mocks. PowerMock can be used if needed, which is simple and practical, and annotation injection can be used with Spring.

@MockBean

SpringBoot replaces the annotated Bean with the native Bean in the IOC container when it performs unit tests.

For example, in ProjectService, a database query is performed using the selectById method of ProjectMapper:

@Service
public class ProjectService {

    @Autowired
    private ProjectMapper mapper;

    public ProjectDO detail(String id) {
        returnmapper.selectById(id); }}Copy the code

At this point we can Mock a ProjectMapper object to replace the native beans in the IOC container to simulate database query operations such as:

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProjectServiceTest {
  
    @MockBean
    private ProjectMapper mapper;
    @Autowired
    private ProjectService service;

    @Test
    public void detail(a) {
        ProjectDemoDO model = new ProjectDemoDO();
        model.setId("1");
        model.setName("dubbo-demo");
        Mockito.when(mapper.selectById("1")).thenReturn(model);
        ProjectDemoDO entity = service.detail("1");
        assertThat(entity.getName(), containsString("dubbo-demo")); }}Copy the code

Mockito common methods

More usage of Mockito can be viewed → official documentation

The mock object ()
List list = mock(List.class);

Copy the code
Verify () to verify interaction
@Test
public void mockTest(a) {
	List list = mock(List.class);
  list.add(1);
  // Verify that the Add (1) interaction occurs
  Mockito.verify(list).add(1);
}

Copy the code
When () simulates the expected result
@Test
public void mockTest(a) {
  List list = mock(List.class);
  when(mock.get(0)).thenReturn("hello");
  assertThat(mock.get(0),is("hello"));
}

Copy the code
DoThrow () simulates throwing an exception
@Test(expected = RuntimeException.class)
public void mockTest(a){
  List list = mock(List.class);
  doThrow(new RuntimeException()).when(list).add(1);
  list.add(1);
}

Copy the code
@ Mock annotations

In the above test we mock a List object in each test method. To avoid repeated mocks and make the test class more readable, we can use the following annotations to quickly mock the object:

// @RunWith(MockitoJUnitRunner.class) 
public class MockitoTest {
    @Mock
    private List list;

    public MockitoTest(a){
      	// Initialize the @mock annotation
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void shorthand(a){
        list.add(1);
        verify(list).add(1); }}Copy the code
The when() argument matches
@Test
public void mockTest(a){
	Comparable comparable = mock(Comparable.class);
  // The default returns different results for different arguments
  when(comparable.compareTo("Test")).thenReturn(1);
  when(comparable.compareTo("Omg")).thenReturn(2);
  assertThat(comparable.compareTo("Test"),is(1));
  assertThat(comparable.compareTo("Omg"),is(2));
  // The default value is returned if there is no default value
   assertThat(list.get(1),is(999));
   assertThat(comparable.compareTo("Not stub"),is(0));
}

Copy the code
Answer Modify return default expectations for unpreset calls
@Test
public void mockTest(a){
  // The mock object uses Answer to return the default expected value for unpreset calls
  List list = mock(List.class,new Answer() {
    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
      return 999; }});// The following get(1) is not preset and normally returns NULL, but uses Answer to change the default expected value
  assertThat(list.get(1),is(999));
  // The following size() is not preset and normally returns 0, but uses Answer to change the default expected value
  assertThat(list.size(),is(999));
}

Copy the code
Spy () monitors real objects

A Mock is not a real object; it just creates a dummy object and sets its behavior. Spy is a real object, but it sets the behavior of the object.

@Test(expected = IndexOutOfBoundsException.class)
public void mockTest(a){
  List list = new LinkedList();
  List spy = spy(list);
  // The following default spy.get(0) will report an error because get(0) of the real object will be called, so an out-of-bounds exception will be thrown
  when(spy.get(0)).thenReturn(3);
  // Use doreturn-when to avoid when-thenReturn calling the real object API
  doReturn(999).when(spy).get(999);
  // Preset size() expected value
  when(spy.size()).thenReturn(100);
  // Call the API of the real object
  spy.add(1);
  spy.add(2);
  assertThat(spy.size(),is(100));
  assertThat(spy.size(),is(1));
  assertThat(spy.size(),is(2));
  verify(spy).add(1);
  verify(spy).add(2);
  assertThat(spy.get(999),is(999));
}

Copy the code
The reset () reset the mock
@Test
public void reset_mock(a){
  List list = mock(List.class);
  when(list.size()).thenReturn(10);
  list.add(1);
	assertThat(list.size(),is(10));
  // Reset the mock to clear all interactions and presets
  reset(list);
  assertThat(list.size(),is(0));
}

Copy the code
Times () Validates the number of calls
@Test
public void verifying_number_of_invocations(a){
  List list = mock(List.class);
  list.add(1);
  list.add(2);
  list.add(2);
  list.add(3);
  list.add(3);
  list.add(3);
  // Verify if it is called once, equivalent to times(1) below
  verify(list).add(1);
  verify(list,times(1)).add(1);
  // Verify if it is called twice
  verify(list,times(2)).add(2);
  // Verify if it is called 3 times
  verify(list,times(3)).add(3);
  // Verify that it has never been called
  verify(list,never()).add(4);
  // Validate at least once
  verify(list,atLeastOnce()).add(1);
  // Validate at least 2 times
  verify(list,atLeast(2)).add(2);
  // Validate up to 3 times
  verify(list,atMost(3)).add(3);
}

Copy the code
InOrder () verifies the execution order
@Test
public void verification_in_order(a){
  List list = mock(List.class);
  List list2 = mock(List.class);
  list.add(1);
  list2.add("hello");
  list.add(2);
  list2.add("world");
  // Place the mock object to be sorted into InOrder
  InOrder inOrder = inOrder(list,list2);
  // The following code cannot be reversed to verify the execution order
  inOrder.verify(list).add(1);
  inOrder.verify(list2).add("hello");
  inOrder.verify(list).add(2);
  inOrder.verify(list2).add("world");
}

Copy the code
VerifyZeroInteractions () verifies zero interaction behavior
 @Test
 public void mockTest(a){
   List list = mock(List.class);
   List list2 = mock(List.class);
   List list3 = mock(List.class);
   list.add(1);
   verify(list).add(1);
   verify(list,never()).add(2);
   // Verify zero interaction behavior
   verifyZeroInteractions(list2,list3);
 }

Copy the code
VerifyNoMoreInteractions () verifies redundant interactions
@Test(expected = NoInteractionsWanted.class)
public void mockTest(a){
  List list = mock(List.class);
  list.add(1);
  list.add(2);
  verify(list,times(2)).add(anyInt());
  // Check for any unvalidated interactions, since add(1) and add(2) are both validated by anyInt() above, so the following code passes
  verifyNoMoreInteractions(list);

  List list2 = mock(List.class);
  list2.add(1);
  list2.add(2);
  verify(list2).add(1);
  // Check for unvalidated interactions, since add(2) is not validated, the following code will fail to throw an exception
  verifyNoMoreInteractions(list2);
}

Copy the code