1

After getting used to unit testing, if some code is not tested before submitting, it always feels empty in my heart, and there is no confidence to speak of.

The official annotations provided by Spring Boot combined with the powerful Mockito can solve most of the testing requirements. But it seems that the aspect under the agency model is not satisfactory.

Scenario simulation

Suppose we currently have a StudentControllor getNameById method is stored in the controller.


@RestController
public class StudentController {

  @GetMapping("{id}")
  public Student getNameById(@PathVariable Long id) {
    return new Student("测试姓名");
  }
  public static class Student {
    private String name;

    public Student(String name) {
      this.name = name;
    }

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }
  }
}

In the absence of contact, we visit this method to get the corresponding student information with the test name.

Create a section

Now, we use the aspect method to append a Yz suffix to the back of the returned name.

@Aspect
@Component
public class AddYzAspect {
  @AfterReturning(value = "execution(* club.yunzhi.smartcommunity.controller.StudentController.getNameById(..))",
      returning = "student")
  public void afterReturnName(StudentController.Student student) {
    student.setName(student.getName() + "Yz");
  }
}

test

If we use the normal test method to directly assert that the returned name is of course feasible:


@SpringBootTest
class AddYzAspectTest {
  @Autowired
  StudentController studentController;
  @Test
  void afterReturnName() {
    Assertions.assertEquals(studentController.getNameById(123L).getName(), "测试姓名Yz");
  }
}

But often the logic in the aspect is not so simple. In the actual test, we actually do not need to care about what happens in the aspect (what happens should be done in the method of testing the aspect). Our main concern here is whether the aspect is successfully executed, and the corresponding assertion is established to prevent accidentally invalidating the current aspect in the code iteration process later in the day.

MockBean

Spring Boot provides us MockBean directly Mock a fall Bean . When testing section successfully executed, we do not care StudentController in getNameById() perform logical approach, it is suitable for appropriate MockBean declared.

 @SpringBootTest
 class AddYzAspectTest {
-  @Autowired
+  @MockBean
   StudentController studentController;

But MockBean is not suitable for testing the aspect. This is because MockBean will directly ignore the annotations of the relevant aspect when generating a new agent, resulting in the direct failure of the aspect.

At the same time, MockBean can be used to simulate Controller , an error will occur if it is used to simulate Aspect.

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration': BeanPostProcessor before instantiation of bean failed; 

MockSpy

In addition to MockBean , Spring Boot also prepares to carry the real Bean , but the Bean can be dropped at any time as required by Mock . At the same time, the Bean generated using this annotation will not destroy the original section.

class AddYzAspectTest {
  @SpyBean
  StudentController studentController;

  @SpyBean
  AddYzAspect addYzAspect;

But is Note that @SpyBean has successfully generated two Mock that can be dropped by Bean , but the corresponding aspect method will be automatically called once when the corresponding Mock The following code example will automatically call AddYzAspect in afterReturnName method.

  @Test
  void afterReturnName() {
    StudentController.Student student = new StudentController.Student("test");
    Mockito.doReturn(student).when(this.studentController).getNameById(123L); 👈 
  }

By this time due to Mock method declared out of the return value, so Mockito will be used null to do is to access the return value AddYzAspect in afterReturnName method. So there will be a NullPointerException exception at this time:

java.lang.NullPointerException
    at club.yunzhi.smartcommunity.aspects.AddYzAspect.afterReturnName(AddYzAspect.java:14)

Therefore, before the Mock is cut, we need to Mock off the related methods of the aspect in advance. At the same time, when the Mock is cut, null will be used as the return value of the method, so directly write null :

  @Test
  void afterReturnName() {
    Mockito.doNothing().when(this.addYzAspect).afterReturnName(null);
    Mockito.doReturn(null).when(this.studentController).getNameById(123L);

Complete test code

@SpringBootTest
class AddYzAspectTest {
  @SpyBean
  StudentController studentController;

  @SpyBean
  AddYzAspect addYzAspect;

  @Test
  void afterReturnName() {
    Mockito.doNothing().when(this.addYzAspect).afterReturnName(null);
    Mockito.doReturn(null).when(this.studentController).getNameById(123L);
    Mockito.verify(this.addYzAspect, Mockito.times(1)).afterReturnName(null);
  }
}

潘杰
3.1k 声望241 粉丝