习惯了单元测试当前,一些代码在提交前如果不测试一下总是感觉心里面空空的,没有底气可言。
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); }}