Spring Aspect not triggered in unit test
Asked Answered
I

3

5

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() {

}

Then in a controller we have:

@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!

Infraction answered 30/10, 2013 at 18:22 Comment(2)
Update. If I change to webAppContextSetup from standaloneSetup the annotations ARE triggered, but obviously in that case the controller being called is not the one the the mocks injected so it executes with real service classes. So either the problem is triggering annotations with standaloneSetup, or injecting mocks with webAppContextSetupInfraction
Further update. It seems that even manually setting the fields of the controller under test fails too. Something like this: MyController controller = wac.getBean(MyController.class); Returns a different instance than the one used when the request is processed. I see the constructor for MyController being called twiceInfraction
I
7

OK.. so the solution eventually presented itself from.. you guessed it.. reading the documentation :/

http://docs.spring.io/spring-framework/docs/3.2.0.BUILD-SNAPSHOT/reference/htmlsingle/#spring-mvc-test-framework

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 {
...
Infraction answered 30/10, 2013 at 21:36 Comment(4)
this doesn't look like a unit test anymore. It's more like an integration testSaccharine
@Saccharine Depends what you consider to be a unit test: jasonpolites.github.io/tao-of-testing/preface-1.1.htmlInfraction
Tx @JasonPolites great helpEscritoire
"Importantly the MyService instance is @Autowired into the test so it can be orchestrated." Upvoted. Saved me from the headacheAmatol
F
1

you need to enable aop in test:

@EnableAspectJAutoProxy
@RunWith(SpringJUnit4ClassRunner.class)
public class MyControllerTest {

}
Flautist answered 5/8, 2021 at 2:49 Comment(0)
B
0

I had a similar setup using MockMVC to perform integration tests through the layers. The Annotation was working fine without any extra code until I renamed the annotation's package. It stopped working. The annotation would not execute. After banging my head for a while, I realized I had already solved this once: ComponentScan the annotation's package.

You probably have a similar statement in one of you application's configuration class files. You will need to repeat any of those types of declarations here in your Test Class.

@SpringBootTest
@AutoConfigureMockMvc
@ComponentScan("annotation.package")
public class MyControllerTest {

}
Benefaction answered 18/2, 2021 at 22:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.