verifyNoInteractions works with mock but not with spy - unit-testing

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 ?

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.

Increase jUnit speed execution when using mockk

I am having some issue with my jUnit tests. They are running very slowly.
Context: I have around 100 unit tests that are taking more than 2 minutes to run!
I am mocking dependencies using mockk library. I noticed that the one that I mock dependencies are the slowest. I understand that mocking object can take some time, but that is just too much. I must have something wrong...
Here is the "pseudo code" for one of my unit test class:
internal class MyClassImplTest {
#get:Rule
val coroutineTestRule: CoroutineTestRule = CoroutineTestRule()
#MockK
lateinit var dependencie1: Dependencie1
#MockK
lateinit var dependencie2: Dependencie2
lateinit var myClassImpl: MyClassImpl
#Before
fun setUp() {
MockKAnnotations.init(this)
myClassImpl= MyClassImpl(
FakeCoroutinesDispatcherProvider(coroutineTestRule.testDispatcher),
dependencie1,
dependencie2
)
}
#Test
fun `should not be null`() {
assertNotNull(myClassImpl)
}
#Test
fun `should do something`() = coroutineTestRule.runBlockingTest {
//mock method call with coEvery {}
val isConnected = myClassImpl.connect(options)
assertFalse(isConnected)
//verify method calls with coVerify {}
}
#Test
fun `should do something else`() = coroutineTestRule.runBlockingTest {
//mock method call with coEvery {}
val isConnected = myClassImpl.connect(options)
assertFalse(isConnected)
//verify method calls with coVerify {}
}
}
For instance, this one was the one that took longer in the image above (38s).
This is not deterministic. It is not always the same that is the slowest, but when I run all of them it takes always more than 2 minutes to run...
So what am I missing here?

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 Aspect not triggered in unit test

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