Issue
OK, we're talking Spring (3.2.0) MVC
We have an pointcut defined to be triggered "around" an annotation like so:
@Around("@annotation(MyAnnotation)")
public void someFunction() {
}
@Controller
@Component
@RequestMapping("/somepath")
public class MyController {
@Autowired
private MyService service;
...
@MyAnnotation
@RequestMapping(value = "/myendpoint", method = RequestMethod.POST, produces = "application/json")
@ResponseBody
public Object myEndpoint(@RequestBody MyRequestObject requestObject, HttpServletRequest request, HttpServletResponse response) {
...
return service.doSomething(requestObject);
}
}
Then we have a unit test that looks like this:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {"../path/to/applicationContext.xml"})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class})
public class MyControllerTest {
private MockMvc mockMvc;
@InjectMocks
private MyController controller;
@Mock
private MyService myService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@Test
public void myTest() {
MyRequest request = new MyRequest();
MyResponse response = new MyResponse();
String expectedValue = "foobar";
Mockito.when(myService.doSomething((MyRequest) Mockito.any())).thenReturn(response);
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/myendpoint");
String request = IOUtils.toString(context.getResource("classpath:/request.json").getURI());
builder.content(request);
builder.contentType(MediaType.APPLICATION_JSON);
mockMvc.perform(builder)
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.someKey").value(expectedValue));
Mockito.verify(myService, Mockito.times(1)).doSomething((MyRequest) Mockito.any());
}
}
The test runs fine, but the aspect defined around the annotation (MyAnnotation) does not execute. This executes just fine when the endpoint is triggered by a real request (e.g. when running in a servlet container) but just doesn't fire when running in the test.
Is this a particular "feature" of MockMvc that it doesn't trigger aspects?
FYI our applicationContext.xml is configured with:
<aop:aspectj-autoproxy/>
and as I mentioned the aspects do actually work in reality, just not in the test.
Anyone know how to get these aspects to fire?
Thanks!
Solution
OK.. so the solution eventually presented itself from.. you guessed it.. reading the documentation :/
Furthermore, you can inject mock services into controllers through Spring configuration, in order to remain focused on testing the web layer.
So the final solution looks like this:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {"testContext.xml","../path/to/applicationContext.xml"})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class})
public class MyControllerTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext wac;
@Autowired
private MyService myService;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void myTest() {
MyRequest request = new MyRequest();
MyResponse response = new MyResponse();
String expectedValue = "foobar";
Mockito.when(myService.doSomething((MyRequest) Mockito.any())).thenReturn(response);
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/myendpoint");
String request = IOUtils.toString(context.getResource("classpath:/request.json").getURI());
builder.content(request);
builder.contentType(MediaType.APPLICATION_JSON);
mockMvc.perform(builder)
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.someKey").value(expectedValue));
Mockito.verify(myService, Mockito.times(1)).doSomething((MyRequest) Mockito.any());
}
}
Then you simply define a context file for this test testContext.xml
that has the mock of the service object:
<bean id="myService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.mypackage.MyService"/>
</bean>
Importantly the MyService instance is @Autowired
into the test so it can be orchestrated.
This allows you to mock out any instances you like, whether they are in service classes, aspects etc as long as you name the bean appropriately. So in this case the MyService
would be declared as:
@Component("myService")
public class MyService {
...
Answered By - Jason Polites
Answer Checked By - Candace Johnson (JavaFixing Volunteer)