习惯了单元测试当前,一些代码在提交前如果不测试一下总是感觉心里面空空的,没有底气可言。

Spring Boot提供的官网正文联合弱小的Mockito可能解决大部分在测试方面的需要。但貌似对于代理模式下的切面却并不如意。

情景模仿

假如咱们以后有一个StudentControllor,该控制器中存一个getNameById办法。

@RestControllerpublic 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;    }  }}

在没有切背后,咱们拜访该办法将失去相应带有测试姓名的学生信息。

建设切面

当初,咱们应用切面的办法在返回的名字后盾追加一个Yz后缀。

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

测试

如果咱们应用一般测试的办法来间接断言返回的姓名当然是可行的:

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

但往往切面中的逻辑并非这么简略,在理论的测试中其实咱们也实现没有必要关怀在切面中到底产生了什么(产生了什么应该在测试切面的办法中实现)。咱们在此次要关怀的是切面是否胜利的被执行了,同时建设相应的断言,以避免在日前面的代码迭代过程中不小心使以后的切面生效。

MockBean

Spring Boot为咱们提供了MockBean来间接Mock掉某个Bean。在测试切面是否胜利执行时,咱们并不关怀StudentController中的getNameById()办法的执行逻辑,所以实用于适合MockBean来申明。

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

MockBean并不适宜于测试切面,这是因为MockBean在生成新的代理时将间接疏忽掉相干切面的注解,导致切面间接生效。

同时MockBean尽管能够用于来模仿Controller,但如果用它来模仿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

除了MockBean以外,Spring Boot还筹备了携带了真正的Bean,但该Bean又能够随时按需要Mock掉的,同时应用该注解生成的Bean并不会毁坏原来的切面。

class AddYzAspectTest {  @SpyBean  StudentController studentController;  @SpyBean  AddYzAspect addYzAspect;

但在这须要留神@SpyBean尽管胜利的生成了两个能够被Mock掉的Bean,但在执行相应的Mock办法时其对应的切面办法会主动调用一次。比方以下代码将主动调用AddYzAspect中的afterReturnName办法。

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

而此时因为被Mock掉的办法申明了返回值,所以Mockito则会应用null来做为返回值来拜访AddYzAspect中的afterReturnName办法。所以此时则会产生了个NullPointerException异样:

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

所以咱们在Mock被切的办法前,须要提前把切面的相干办法Mock掉,同时因为Mock被切办法时会以null来做为办法的返回值,所以在相应的参数上间接写入null即可:

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

残缺测试代码

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