习惯了单元测试当前,一些代码在提交前如果不测试一下总是感觉心里面空空的,没有底气可言。
Spring Boot 提供的官网正文联合弱小的 Mockito 可能解决大部分在测试方面的需要。但貌似对于代理模式下的切面却并不如意。
情景模仿
假如咱们以后有一个 StudentControllor
,该控制器中存一个getNameById
办法。
@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;}
}
}
在没有切背后,咱们拜访该办法将失去相应带有测试姓名的学生信息。
建设切面
当初,咱们应用切面的办法在返回的名字后盾追加一个 Yz
后缀。
@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");
}
}
测试
如果咱们应用一般测试的办法来间接断言返回的姓名当然是可行的:
@SpringBootTest
class 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);
残缺测试代码
@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);
}
}