Spring Aspect not triggered in unit test - unit-testing

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!

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 {
...

you need to enable aop in test:
#EnableAspectJAutoProxy
#RunWith(SpringJUnit4ClassRunner.class)
public class MyControllerTest {
}

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 {
}

Related

Does The program flow go deeper into the bean being mocked in MockMvc?

From what I understand about mocking, the test should not go deeper into the bean being mocked. For example the control flow shouldn't go into the function apiService.getSomeData() and instead it should just return the string "Hello there".
But is that how mocking works or does the program keep going deeper and should I be able to see the print statements of getSomeData() in the stdout?
When I actually run the code below, it doesn't go deeper. But is that how it's supposed to work?
Suppose this is the Rest Controller Code:
#RestController
#RequestMapping(value = "/testing")
public class ApiController {
#Autowired
ApiService service;
#PostMapping(path = "/events/notifications",consumes = "application/json", produces = "application/json" )
public ResponseEntity<String> checkMapping(#Valid #RequestBody String someData, #RequestHeader(value="X-User-Context") String xUserContext) throws Exception {
String response = service.getSomeData(someData);
return ResponseEntity.status(HttpStatus.OK).body(response);
}
}
Suppose this is the Controller test code:
#WebMvcTest(ApiController.class)
public class ApiControllerTest {
#Autowired
MockMvc mockMvc;
#Autowired
ObjectMapper mapper;
#MockBean
ApiService apiService;
#Test
public void testingApi() throws Exception {
Mockito.when(apiService.getSomeData("")).thenReturn("Hello there");
MockHttpServletRequestBuilder mockRequest = MockMvcRequestBuilders.post("/testing/events/notifications")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.header("X-User-Context","something")
.content("something");
mockMvc.perform(mockRequest)
.andExpect(status().isBadGateway());
}
}
Suppose this is the Api Service code:
#Service
public class ApiServiceImpl implements ApiService{
#Override
public String getSomeData(String data) throws Exception {
System.out.println("Going deeper in the program flow);
callThisFunction();
return "Some data";
}
public void callThisFunction(){
System.out.println("Going two levels deeper");
}
}
In your test you are not talking to ApiServiceImpl at all, but an instance that is created by mockito and that is also implementing the ApiService interface. Therefore, your implementation of getSomeData() is not executed at all. That's what mocking is about. You create a "mock" implementation (or let a tool like mockito do it for you) of the thing you do not want to be executed and inject it instead of the "real" thing.

verifyNoInteractions works with mock but not with spy

Eg.
#Component open class AService {
#Autowired private lateinit var anotherService: AnotherService
...
...
fun methodCall() {
anotherService.methodCall()
}
}
#Service
open class AnotherService{
#Autowired private lateinit var aRepo: ARepo
fun methodCall() {
aRepo.something()
}
}
#WebAppConfiguration
#ActiveProfiles("test")
#RunWith(SpringJUnit4ClassRunner::class)
#SpringBootTest(classes = [TestAppConfig::class])
open class MyTest {
#MockBean private lateinit var aRepo: ARepo
#SpyBean private lateinit var anotherService: AnotherService
#Autowired private lateinit var aservice: AService
#Before fun setUp() {
MockitoAnnotations.openMocks(this)
}
#Test
fun shouldNotInteractWithARepo() {
aservice.methodCall() // Call to Test
verifyNoInteractions(aRepo) // This works and fails as expected
//verifyNoInteractions(anotherService) // Expect to fail since there is actually a call being made on this spy but it passes.
}
aRepo does an external network call. I have mocked it at an application level. Similarly anotherService is the encapsulating service in which aRepo makes the network call. I have spied anotherService because I want to verify at times that there is no interaction with it and at times, I want its actual behavior.
In the examples shown, I have only considedred the first case of no interaction. Testing of the actual behavior with #Spy works fine.
verifyNoInteractions on the spied field(anotherService) always passes. Why is this ?. The same when I use it on the Mocked field(aRepo), it works and fails as expected.
How do I setup in a way that if anotherService.methodCall() happens, the test should fail ?

Mock is failing in If condition

In my controller, i am doing a validation check in if block using a validator class.
Since validation code is not complete yet i just want to test that if
validation was successful the response should be 201(Success).
For this i am mocking the behaviour of validator object to return true
i.e its valid.
The problem is even if i have mocked the validator object behaviour to not enter if block so it doesn't throw validation Exception, its actually entering and throwing exception. That means mocking hasn't worked. I know its bad to assume the method implementation for testing, but i want to know why its not working.
myController.class
#RestController
#RequestMapping("/v1/api")
public class myController {
#Autowired
private RequestService requestService;
#Autowired
RequestValidator validator;
#PostMapping(value = "")
#ResponseStatus(code = HttpStatus.CREATED)
public #ResponseBody
BaseResponse<Response> create(#RequestBody Request<Data> request){
if(!validator.isValidRequestObject(request))
{
throw new RuntimeException("invalid request");
}
Response data = requestService.submitData(request);
BaseResponse<Response> response = new BaseResponse<Response>();
response.setData(data);
response.setMessage("Created Successfully");
return response;
}
ControllerTest.class
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class ControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
Request<Data> request;
#MockBean
RequestService service;
#MockBean
RequestValidator validator;
ObjectMapper mapper = new ObjectMapper();
#Test
public void createTest() throws Exception {
when(validator.isValidRequestObject(request)).thenReturn(true);
when(service.submitData(request)).thenReturn(null);
this.mockMvc.perform(post("/v1/api")
.content(mapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated());
}
If i remove the throw new exception line, test passes.
i assume that since i have mocked validator behaviour in myController
that should not enter if block and hence not throw Exception.
But its entering the if block and throwing exception defying the mocked behaviour.

Wanted but not invoked: Mockito and Retrofit

I'm trying to unit test a call to my api using Mockito.
I took a look at all the issues already mentioned here on S.O together with the solutions, but so far, none of them have been conclusive.
MyService is an interface which holds several resources. Here's a sample:
public interface MyService {
#GET("/myresource")
Call<MyResponse> getDataFromServer();
}
Inside my Application class, I have a static class which returns an instance of MyService
public static MyService getApiService() {
return mApiService;
}
So, from there on inside one of my classes, I make the call to the web service:
Call<MyResponse> call = getApiService.getDataFromServer();
call.enqueue(myCallback)
The rest follows with the callback method being called....
Here's my test class:
#RunWith(AndroidJUnit4.class)
public class SampleTest {
#Mock
private MyService mService;
#Captor
private ArgumentCaptor<Callback<MyResponse>> callbackArgumentCaptor;
#Mock
private Call<MyResponse> mockCall;
// Rule to trigger the creation of #Mock annotated objects.
#Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
#Test
public void testDoAction() throws NullInsteadOfMockException {
when(mService.doSomeAction()).thenReturn(mockCall);
mService.doSomeAction();
verify(mockCall).enqueue(callbackArgumentCaptor.capture());
}
}
Here's the error I have once I run the test:
Wanted but not invoked:
mockCall.enqueue(
<Capturing argument>
);
Actually, there were zero interactions with this mock.
I have the same error even if use MockitoJunitRunner (in place of AndroidJunitRunner) and initialize my mock objects inside a setup method I define like this:
#Before
public void setUp() throws Exception{
MockitoAnnotations.initMocks(this);
}
Mockito version : 2.7.19
I want to be able to test the API response, so I mocked the API service, defined a captor for the retrofit Callback
It's a bit strange what you're trying to do here. You're testing a class, but you mock it. You should test the real class - MyService. I assume that your service looks a bit like:
public class MyService {
private final Call<MyResponse> call;
public MyService(Call<MyResponse> call) {
this.call = call;
}
public void doSomeAction() {
call.enqueue(...);
}
}
Ideally you should have something like:
#RunWith(AndroidJUnit4.class)
public class SampleTest {
private MyService mService;
#Captor
private ArgumentCaptor<Callback<MyResponse>> callbackArgumentCaptor;
#Mock
private Call<MyResponse> mockCall;
#Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
#Before
public void setUp() throws Exception{
MockitoAnnotations.initMocks(this);
mService = new MyService(mockCall);
}
#Test
public void testDoAction() throws NullInsteadOfMockException {
mService.doSomeAction();
verify(mockCall).enqueue(callbackArgumentCaptor.capture());
}
}
So the idea is to mock all the dependencies of the class you're unit testing and pass them to the class somehow. Here I'm injecting them in the constructor. I don't know if this is the case for you, but a setter or a field works too.
Then the test simply calls the real method in the service class and if this method is suppose to enqueue the call, then the verify should pass.
The reason it doesn't work in your case is because you're mocking the service so when you call mService.doSomeAction() this doesn't call your implementation, which I suppose should call enqueue. This is why the verify fails. In other words, it's true that enqueue is never called on the call object.

Spring boot mocking static methods with PowerMock in Integration test

I'm writing integration test on a RestController in SpringBoot.
Normally I would run with SpringRunner.class, but when it comes to Mock a static method I need to use PowerMock.
The strange fact is that when I run the single tests, they individually pass (but returns error messages), when I try to run the entire test class, no test passes and it returns the same error message.
#RunWith(PowerMockRunner.class)
#PrepareForTest({JwtUtils.class})
//#PowerMockRunnerDelegate(SpringRunner.class) THIS DOESN'T WORK!!!
#SpringBootTest(classes = SpringBootJwtApplication.class)
public class RestAccessIntegrationTest {
#Autowired #InjectMocks
RestController restController;
#Mock
HttpServletRequest request;
#Test
public void operationsPerAccountWhenSuccessfulTest(){
mockStatic(JwtUtils.class);
when(JwtUtils.myMethod(request)).thenReturn("blabla");
String expected = ... ;
String actual = restController.getOperations();
assertEquals(actual, expected);
}
}
If I run the test or the entire class I get an error of this type:
Exception in thread "main" java.lang.NoSuchMethodError: org.powermock.core.MockRepository.addAfterMethodRunner(Ljava/lang/Runnable;)at org.powermock.api.mockito.internal.mockcreation.MockCreator.mock(MockCreator.java:50)
If I uncomment #PowerMockRunnerDelegate(SpringRunner.class) there it comes this other error:
Exception in thread "main" java.lang.NoClassDefFoundError: org/powermock/core/testlisteners/GlobalNotificationBuildSupport$Callback
at org.powermock.modules.junit4.internal.impl.DelegatingPowerMockRunner.run(DelegatingPowerMockRunner.java:139)
In the when method, try using any(HttpServletRequest.class) instead of the request mock object. Also use MockHttpServletRequest instead of mocking HttpServletRequest. This should work,
#RunWith(PowerMockRunner.class)
#PrepareForTest(JwtUtils.class)
#PowerMockIgnore( {"javax.management.*"})
public class RestAccessIntegrationTest {
#InjectMocks
private RestController restController;
private MockHttpServletRequest request;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
request = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(
new ServletRequestAttributes(request));
}
#Test
public void operationsPerAccountWhenSuccessfulTest() {
mockStatic(JwtUtils.class);
when(JwtUtils.myMethod(any(HttpServletRequest.class)))
.thenReturn("blabla");
String expected = ... ;
// does your getOperations take HttpServletRequest
// as parameter, then controller.getOperations(request);
String actual = restController.getOperations();
assertEquals(actual, expected);
}
}
It was due to incompatibility in library version of PowerMock and Mockito. I suggest to check the compatibility version table provided by PowerMock team or to switch to JMockit to mock static and private methods.