Reputation: 1043
This has been asked before, but I can not see quite the same question & certainly can't see the solution.
I am trying to mock a database Dao call that saves an object in to a DB. The Dao calls returns the new PK, but also modifies the passed object by updating the PK.
My test is working as I want except the verify fails saying I am not passing the expected object. yet when I pause in the debugger I can see that the insert is indeed accepting an object with no PK. It is then modifying it & the calling controller can see the changed object.
@Test
public void insertSampleAnalyteLabConfig() throws Exception {
int newInt = 99;
SampleAnalyteLabConfig sampleAnalyteLabConfig = createMockSampleAnalyteLabConfig(null);
SampleAnalyteLabConfig sampleAnalyteLabConfig2 = createMockSampleAnalyteLabConfig(null);
when(sampleAnalyteLabConfigService.save(sampleAnalyteLabConfig)).thenAnswer( invocation -> {
SampleAnalyteLabConfig foo = (SampleAnalyteLabConfig) invocation.getArguments()[0];
foo.setId(newInt);
return new Long(newInt);
}
);
mockMvc.perform(post("/api/lab/sampleanalytelab")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(getBytes(sampleAnalyteLabConfig))).andExpect(status().isCreated());
verify(sampleAnalyteLabConfigService, times(1)).save(sampleAnalyteLabConfig2);
}
The test output is as follows:
Argument(s) are different! Wanted: sampleAnalyteLabConfigService.save( SampleAnalyteLabConfig{{id=0}sampleId=2, analyteId=3, labId=1, numberOfRounds=null, absolutePerformanceScoreWarning=null} ); -> at com.foo.controllers.LabControllerTest.insertSampleAnalyteLabConfig(LabControllerTest.java:236) Actual invocation has different arguments: sampleAnalyteLabConfigService.save( SampleAnalyteLabConfig{{id=99}sampleId=2, analyteId=3, labId=1, numberOfRounds=null, absolutePerformanceScoreWarning=null} ); -> at com.foo.controllers.LabController.saveSampleAnalyteLabConfig(LabController.java:165)
Comparison Failure:
Argument(s) are different! Wanted: sampleAnalyteLabConfigService.save( SampleAnalyteLabConfig{{id=0}sampleId=2, analyteId=3, labId=1, numberOfRounds=null, absolutePerformanceScoreWarning=null} ); -> at com.foo.controllers.LabControllerTest.insertSampleAnalyteLabConfig(LabControllerTest.java:236) Actual invocation has different arguments: sampleAnalyteLabConfigService.save( SampleAnalyteLabConfig{{id=99}sampleId=2, analyteId=3, labId=1, numberOfRounds=null, absolutePerformanceScoreWarning=null} ); -> at com.foo.controllers.LabController.saveSampleAnalyteLabConfig(LabController.java:165)
at com.foo.controllers.LabControllerTest.insertSampleAnalyteLabConfig(LabControllerTest.java:236)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Disconnected from the target VM, address: '127.0.0.1:61076', transport: 'socket'
Process finished with exit code -1
Upvotes: 1
Views: 1672
Reputation: 1043
So the solution I have gone to - thanks to @Jeff Bowman's help in the discussion above is to embed the verify within the Answer().
I have put it here as a separate answer for clarity.
@Test
public void insertSampleAnalyteLabConfig() throws Exception {
int newInt = 99;
SampleAnalyteLabConfig sampleAnalyteLabConfig = createMockSampleAnalyteLabConfig(null);
when(sampleAnalyteLabConfigService.save(sampleAnalyteLabConfig)).thenAnswer( invocation -> {
verify(sampleAnalyteLabConfigService, times(1)).save(sampleAnalyteLabConfig);
SampleAnalyteLabConfig foo = (SampleAnalyteLabConfig) invocation.getArguments()[0];
foo.setId(newInt);
return new Long(newInt);
}
);
mockMvc.perform(post("/api/lab/sampleanalytelab")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(getBytes(sampleAnalyteLabConfig))).andExpect(status().isCreated());
}
Upvotes: 0
Reputation: 95634
Mockito uses Java equals
by default, when you pass in direct values to when
or verify
. What you're seeing is that you've created two separate SampleAnalyteLabConfig
instances, you're passing in one instance (which gets modified to add its ID) with a different instance (which isn't modified). Assuming that equals
and hashCode
work, just make sure you're preparing the instance you're verifying against to match the modified instance you're comparing to.
SampleAnalyteLabConfig sampleAnalyteLabConfig = createMockSampleAnalyteLabConfig(null);
SampleAnalyteLabConfig sampleAnalyteLabConfig2 = createMockSampleAnalyteLabConfig(null);
// ...
verify(sampleAnalyteLabConfigService, times(1)).save(sampleAnalyteLabConfig2);
Argument(s) are different! Wanted: sampleAnalyteLabConfigService.save( SampleAnalyteLabConfig{ {id=0} sampleId=2, analyteId=3, labId=1, numberOfRounds=null, absolutePerformanceScoreWarning=null} ); -> at com.foo.controllers.LabControllerTest.insertSampleAnalyteLabConfig(LabControllerTest.java:236)
Actual invocation has different arguments: sampleAnalyteLabConfigService.save( SampleAnalyteLabConfig{ {id=99} sampleId=2, analyteId=3, labId=1, numberOfRounds=null, absolutePerformanceScoreWarning=null} ); -> at com.foo.controllers.LabController.saveSampleAnalyteLabConfig(LabController.java:165)
From the comments:
I know I can setId() on sampleAnalyteLabConfig2 but that is my issue as the save() method is not called with the Id set so asking the verify() to confirm it is called with setId() called is wrong IMO.
Mockito isn't in the business of making copies of objects: When you check an object with verify
, you are checking against the current state of the instance including any modifications that were made to it during or after the call to your service. This is important to remember when considering the three instances of your object: Your sampleAnalyteLabConfig
, your sampleAnalyteLabConfig2
, and the instance that ObjectMapper creates that is then passed into your service.
The verification statement you make will verify the post-save
state of that one instance ObjectMapper creates, including your call to foo.setId(newInt);
from within your Answer. You are not verifying against a snapshot of the instance made during the call, because Mockito doesn't make one.
Generally speaking, I would consider it safe to call setId(newInt)
on your sampleAnalyteLabConfig2
and test against that, since newInt
is a unique integer that is unlikely to come from anywhere except your test: you can infer from a successful verification that the object was passed in and modified exactly and only as you expect it to be. That's certainly what I would do. However, if you want to check the pre-modification state of the instance you pass into save
, you should either remove your state-modifying answer or move your assertion to within the Answer. (You could also clone that instance before you change the state in your answer, and test the clone, but that doesn't tend to have much value other than if you're using an already-written library or helper to pass in that state.)
Upvotes: 2