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);
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。