Spring: cannot inject a mock into class annotated with the @Aspect annotation
mockito, spring, spring-aop, spring-mvc
Solution
The issue here is that your Mock instances and the `ValidSessionChecker` are not Spring beans and so are not being wired into the `ValidSessionChecker` managed by Spring. To make the mocks Spring beans instead probably a better approach will be to create another bean definition file which extends the beans defined in the base configuration file and adds mocks:
test-config.xml:
<beans...>
<import resource="base-springmvc-config.xml"/>
<beans:bean name="usersDatabaseAccessProvider" factory-method="mock" class="org.mockito.Mockito">
<beans:constructor-arg value="..UsersDatabaseAccessProvider"></beans:constructor-arg>
</beans:bean>
And then in your test inject behavior into the mock:
public class AccessControlControllerTestsWithInjectedMocks {
@Autowired
private org.springframework.web.context.WebApplicationContext wac;
private MockMvc mockMvc;
@Autowired
UsersDatabaseAccessProvider usersDatabaseAccessProvider;
@Autowired
ValidSessionChecker validSessionChecker;
....
@Before
public void before() throws Throwable {
//given
MockitoAnnotations.initMocks(this);
when(usersDatabaseAccessProvider.sessionNotExpired("123456")).thenReturn(Boolean.FALSE);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
This should cleanly work.
Problem
I created a Before advice using AspectJ: ``` package test.accesscontrol.permissionchecker; import test.accesscontrol.database.SessionExpiredException; import test.database.UsersDatabaseAccessProvider; import test.common.constants.GlobalConstants; import test.common.model.AbstractRequest; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.beans.factory.annotation.Autowired; @Aspect public class ValidSessionChecker { private static final int REQUEST_PARAMETER_ARGUMENT_POSITION = GlobalConstants.ZERO; private UsersDatabaseAccessProvider usersDatabaseAccessProvider; @Autowired public ValidSessionChecker(UsersDatabaseAccessProvider usersDatabaseAccessProvider) { this.usersDatabaseAccessProvider = usersDatabaseAccessProvider; } @Before("@annotation(test.accesscontrol.permissionchecker.ValidSessionRequired)") public void before(JoinPoint joinPoint) throws Throwable { Object requestParameterObject = joinPoint.getArgs()[REQUEST_PARAMETER_ARGUMENT_POSITION]; AbstractRequest requestParameter = (AbstractRequest) requestParameterObject; String sessionID = requestParameter.getSessionId(); if(!usersDatabaseAccessProvider.sessionNotExpired(sessionID)) throw new SessionExpiredException(String.format("Session expired: %s", sessionID)); } } ``` and a test class: ``` package test.accesscontrol; import test.accesscontrol.database.UsersDatabaseAccessProvider; import test.accesscontrol.permissionchecker.ValidSessionChecker; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.*; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration("file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml") public class AccessControlControllerTestsWithInjectedMocks { @Autowired private org.springframework.web.context.WebApplicationContext wac; private MockMvc mockMvc; @Mock UsersDatabaseAccessProvider usersDatabaseAccessProvider; @InjectMocks ValidSessionChecker validSessionChecker; @InjectMocks AccessControlController accessControlController; @Before public void before() throws Throwable { //given MockitoAnnotations.initMocks(this); when(usersDatabaseAccessProvider.sessionNotExpired("123456")).thenReturn(Boolean.FALSE); this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } @Test public void changePassword_shouldReturnUnauthorizedHttpCodeWhenSessionIsExpired() throws Exception { //when ResultActions results = mockMvc.perform( post("/accesscontrol/changePassword") .contentType(MediaType.APPLICATION_JSON) .content("{\"sessionId\":\"123456\", \"oldPassword\":\"password\", \"newPassword\":\"newPassword\"}") ); //then results.andExpect(status().isUnauthorized()); verify(usersDatabaseAccessProvider, never()).getSessionOwner(anyString()); verify(usersDatabaseAccessProvider, never()).isCurrentPasswordValid(anyString(), anyString()); verify(usersDatabaseAccessProvider, never()).setNewPassword(anyString(), anyString()); } } ``` spring configuration file: ``` <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <mvc:annotation-driven /> <aop:aspectj-autoproxy /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".jsp"/> </bean> <bean class="org.springframework.context.support.ResourceBundleMessageSource" id="messageSource"> <property name="basename" value="messages" /> </bean> <bean id="usersDatabaseAccessProvider" class="test.accesscontrol.database.UsersDatabaseAccessProvider"/> <bean id="accessControlController" class="test.accesscontrol.AccessControlController"> <property name="sessionExpirationTimeInSeconds" value="600"/> </bean> <bean id="validSessionChecker" class="test.accesscontrol.permissionchecker.ValidSessionChecker" /> <bean id="timeDispatcher" class="test.utils.time.TimeDispatcher" scope="singleton" /> </beans> ``` AccessControlController ``` @Controller @RequestMapping("/accesscontrol") public class AccessControlController { ... @RequestMapping(value = "changePassword", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) @ValidSessionRequired public ResponseEntity<Void> changePassword(@Valid @RequestBody ChangePasswordRequest request) throws OperationForbiddenException { String sessionId = request.getSessionId(); String userEmailAddress = usersDatabaseAccessProvider.getSessionOwner(sessionId); String currentPassword = request.getOldPassword(); this.ensureThatCurrentPasswordIsValid(userEmailAddress, currentPassword); usersDatabaseAccessProvider.setNewPassword(userEmailAddress, request.getNewPassword()); return new ResponseEntity<Void>(HttpStatus.OK); } @ExceptionHandler({SessionExpiredException.class}) public ResponseEntity<Void> handleSessionExpiredException(Exception ex) { return new ResponseEntity<Void>(HttpStatus.UNAUTHORIZED); } } ``` When I call mockMvc.perform(...) it should intercept method, throw an exception and return 401 Unauthorized code. Of course it doesn't work, I tried to debug the test and: - After MockitoAnnotations.initMocks(this); there is one instance (mock) of UsersDatabaseAccessProvider assigned to fields in all classes (ValidSessionChecker, AccessControlController and AccessControlControllerTestsWithInjectedMocks). - But when before(JoinPoint joinPoint) is executed usersDatabaseAccessProvider field in the ValidSessionChecker object contains different instance of UsersDatabaseAccessProvider (also the ValidSessionChecker object is different and it's not the mocked version). How can I inject mocked instance of UsersDatabaseAccessProvider into ValidSessionChecker?