After getting used to unit testing, if some codes are not tested before submission, they always feel empty and have no confidence.
The official annotations provided by Spring Boot combined with the powerful Mockito can solve most of the testing requirements. However, it seems that the aspect in agent mode is not satisfactory.
Scenario simulation
Suppose we currently have a studentcontroller in which a getNameById method is stored.
@RestController public class StudentController { @GetMapping("") public Student getNameById(@PathVariable Long id) { return new Student("Test name"); } 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; } } }
Before there is no section, we will access this method to get the corresponding student information with the test name.
Create section
Now, we use the faceted method to append a Yz suffix to the background 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 directly assert the returned name by using the method of ordinary test, it is certainly feasible:
@SpringBootTest class AddYzAspectTest { @Autowired StudentController studentController; @Test void afterReturnName() { Assertions.assertEquals(studentController.getNameById(123L).getName(), "Test name Yz"); } }
However, the logic in the aspect is often not so simple. In fact, in the actual test, we don't need to care about what happened in the aspect (what happened should be completed in the method of testing the aspect). Here, we are mainly concerned about whether the aspect has been successfully executed, and establish corresponding assertions to prevent inadvertently invalidating the current aspect during future code iterations.
MockBean
Spring Boot provides us with mockbeans to directly Mock a Bean. When testing whether the facet is successfully executed, we do not care about the execution logic of the getNameById() method in the StudentController, so it is applicable to be declared by an appropriate MockBean.
@SpringBootTest class AddYzAspectTest { - @Autowired + @MockBean StudentController studentController;
However, MockBean is not suitable for testing facets. This is because MockBean will directly ignore the annotations of relevant facets when generating new agents, resulting in the direct invalidation of facets.
Meanwhile, although 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 is also ready to carry a real Bean, but the Bean can be mocked off at any time as required. At the same time, the Bean generated with this annotation will not destroy the original section.
class AddYzAspectTest { @SpyBean StudentController studentController; @SpyBean AddYzAspect addYzAspect;
However, it should be noted that although @ SpyBean successfully generates two beans that can be dropped by Mock, its corresponding slice method will be called automatically once when the corresponding Mock method is executed. For example, the following code will automatically call the afterReturnName method in AddYzAspect.
@Test void afterReturnName() { StudentController.Student student = new StudentController.Student("test"); Mockito.doReturn(student).when(this.studentController).getNameById(123L); 👈 }
At this time, because the method dropped by Mock declares the return value, Mockito will use null as the return value to access the afterReturnName method in AddYzAspect. Therefore, a NullPointerException exception will occur:
java.lang.NullPointerException at club.yunzhi.smartcommunity.aspects.AddYzAspect.afterReturnName(AddYzAspect.java:14)
Therefore, we need to Mock the relevant methods of the cut plane in advance before mocking the cut method. At the same time, since null will be used as the return value of the method when mocking the cut method, we can write null directly on the corresponding parameters:
@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); } }