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?