Can a mockito spy return stub value? - java

I want to test this class, so it will shows me that I call ws with right params:
class MyService {
public static boolean sendEmail(MyWebService ws) {
if (!ws.sendCustomEmail("me#example.com", "Subject", "Body")) {
throw new RuntimeException("can't do this");
}
// ... some more logic which can return false and should be tested
return true;
}
}
Is there a way to combine mockito spy and thenReturn? I like how spy will show actual methods calls and not just simple message about assertionFailed.
#Test
void myTest() {
MyService spyWs = Mockito.spy(MyWebService.class);
// code below is not working, but I wonder if there is some library
verify(spyWs, once())
.sendCustomEmail(
eq("me#example.com"),
eq("Subject"),
eq("here should be another body and test shou")
)
.thenReturn(true);
MyService::sendEmail(spyWs);
}
What I want as a result is error message showing me the difference between parameters that expected and actual like usual spy's:
Test failed:
sendCustomEmail(eq("me#example.com"), eq("Subject"), eq("here should be another body and test should show diff")) was never called
sendCustomEmail(eq("me#example.com"), eq("Subject"), eq("Body")) was called, but not expected
Expected:
I know I can do just stub and then test for exception, but thats will not show the difference in parameters

When using a Spy, use the doReturn().when() syntax. Also verify after the set-up:
MyService spyWs = Mockito.spy(MyWebService.class);
doReturn(true).when(spyWs).sendCustomEmail(any(), any(), any());
MyService::sendEmail(spyWs);
verify(spyWs, once())
.sendCustomEmail(
eq("me#example.com"),
eq("Subject"),
eq("here should be another body and test shou")
);
// assert that sendMail returned true;
Frankly i dont think you need to verify here, just a boolean assertion would be enough but thats up to you.

Related

Java unit test - exception not being thrown

Trying to write a test that will call my method, when that method makes a call to another method we will throw a custom exception i have made. Here i have simplified it all
2 functions
public MyJsonResponse hello() {
MyJsonResponse response = new MyJsonResponse();
response.setErrorMessage("1");
response.setStatus("some status");
response.setData("1");
response.setHttpResponse(200);
try{
hi();
return response;
}catch (MyServiceException e) {
response.setErrorMessage(e.getMessage());
response.setStatus("error creating");
response.setData("2");
response.setHttpResponse(e.getResponseStatus());
return response;
}
}
public String hi() throws MyServiceException{
LOG.error("Exception");
return "yea";
}
The test I have written is this
#Test
public void myTest() throws Exception {
given(service.hi()).willAnswer( invocation -> { throw new MyServiceException("abc msg",511); });
MyJsonResponse actual = service.hello();
Assert.assertNotNull(actual);
assertEquals(511, actual.getHttpResponse());
}
But unfortunately the result is as follows
java.lang.AssertionError:
Expected :511
Actual :200
Please, be sure that you are using a spy as you want to use the actual code for some methods of your mocked service and just stubbing specific methods of it. Please, see for instance this related SO question about the subject.
Also, consider modifying your test definition to use willThrow instead of willAnswer: as pointed out by #eis, you can still use the later, but the former is more straightforward.
Your code will look similar to this:
#Test
public void myTest() throws Exception {
MyService service = spy(MyService.class);
willThrow(new MyServiceException("abc msg",511))
.given(service)
.hi()
;
// As pointed out by #eis, you can still use willAnswer
// willAnswer(
// invocation -> { throw new MyServiceException("abc msg",511);}
// )
// .given(service)
// .hi()
// ;
MyJsonResponse actual = service.hello();
Assert.assertNotNull(actual);
assertEquals(511, actual.getHttpResponse());
}
regarding what you explain and what your code look like, I am not sure if I have well understood.
Thus, if you want that, your hi() : function throws an exception.
You have to make it first throws an exception. Take a look at code below!
public String hi() throws MyServiceException{
/*LOG.error("Exception");//No don't just log, throw a real exception as below*/
throw new MyServiceException("text here, if your constructor support it or nothing otherwise")
/*return "yea";//Nothing to return? we have just break the code by throwing the exception above*/
}
After that, please be very sure that your 'MyServiceException.getHttpResponse()' will really return 511
For this test to make sense, your hi() call should be done calling another service that you stub/mock in your test class. You're not doing that, so this approach won't work.
You wrote "the real method that hi represents does a lot", so it's about time you extract that to another service.

Save information that was spied by Mockito

I have a JUnit test as:
#Spy
ParallelSender parallelSender = new ParallelSender();
#Test
public void send() {
// making some data...
parallelSender.send(someData);
// check that internal method has been called with Sender Task made from someData
verify(parallelSender).doSend(any(SenderTask.class));
}
I however like to examine that SenderTask contains all the fields exactly as I need them. Can I tell the spy to intercept the doSend call, store its parameters in some array and then continue to real method?
Use the ArgumentCaptor:
#Test
public void send() {
// making some data...
parallelSender.send(someData);
// Define the captor for class
ArgumentCaptor<SenderTask> captor =
ArgumentCaptor.forClass(SenderTask.class);
// Capture input while verifying
verify(parallelSender).doSend(captor.capture());
// Assert
SomeTask result = captor.getValue();
// assertions on result
}
You can use an ArgumentCaptor.
#Captor
ArgumentCaptor<SenderTask> captor;
// or ArgumentCaptor<SenderTask> captor =
// ArgumentCaptor.forClass(SenderTask.class);
#Test public void send() {
// ...
verify(parallelSender).doSend(captor.capture());
SenderTask captured = captor.getValue();
I rarely use argument captor because it's usually not necessary.
Just do this
#Test
public void send() {
//given
SomeData myInput = ...
SenderTask expectedOutput = new SenderTask();
expectedOutput.setSomeField(/*expected field value*/);
//when
parallelSender.send(myInput);
//then
verify(parallelSender).doSend(expectedOutput);
}
The idea behind is to check that "doSend" was called with an expected object.
Note: just make sure that you implemented equals/hash method in SenderTask - or it will not work
Note2: I would suggest avoiding using any() in your unit tests. Usually when your are unit-testing something - you want to be as much precise as possible. So use concrete objects during results verification.
Hopefully it helps

Mockito verify not working on a spied object

I am trying to use verify in a unit test to verify a method gets called on an object that is spied. I can verify the method is actually being called via a log statement and stepping through the code with a debugger. yet a verify(Object,1).method() call reports "Wanted but not invoked:" Also with the assertion uncommented mockTransferService.getCurrentTask() is returning null while again I can verify with the debugger that it is getting set.
Test code is shown below
#Test
public void testInitialPermitGranted() throws Exception {
Config mockConfig = mock(Config.class);
doReturn(path).when(mockConfig).getRequiredStringProperty(Config.WORK_DIR);
mockTransferService = spy(new TransferServiceImpl(mockServiceConnection, mockConfig,
Executors.newSingleThreadScheduledExecutor()));
doReturn(true).when(mockTransferService).doStartTransfer(any());
mockTransferService.enqueueTransfer(mockTransferTask);
Thread.sleep(1000);
//Assert.assertEquals(mockTransferTask, mockTransferService.getCurrentTask());
verify(mockTransferService,times(1)).startTransfer(any());
}
}
Method that is being tested is shown below
#Override
public boolean startTransfer(TransferTask transferTask) {
LOG.debug("StartTransferCalled");
setCurrentTask(transferTask);
return doStartTransfer(transferTask);
}
I'm pretty new to mockito and obviously I am missing something.

First stub is called when adding an additional stub

I have the following object which I want to test:
public class MyObject {
#Inject
Downloader downloader;
public List<String> readFiles(String[] fileNames) {
List<String> files = new LinkedList<>();
for (String fileName : fileNames) {
try {
files.add(downloader.download(fileName));
} catch (IOException e) {
files.add("NA");
}
}
return files;
}
}
This is my test:
#UseModules(mockTest.MyTestModule.class)
#RunWith(JukitoRunner.class)
public class mockTest {
#Inject Downloader downloader;
#Inject MyObject myObject;
private final String[] FILE_NAMES = new String[] {"fail", "fail", "testFile"};
private final List<String> EXPECTED_FILES = Arrays.asList("NA", "NA", "mockContent");
#Test
public void testException() throws IOException {
when(downloader.download(anyString()))
.thenThrow(new IOException());
when(downloader.download("testFile"))
.thenReturn("mockContent");
assertThat(myObject.readFiles(FILE_NAMES))
.isEqualTo(EXPECTED_FILES);
}
public static final class MyTestModule extends TestModule {
#Override
protected void configureTest() {
bindMock(Downloader.class).in(TestSingleton.class);
}
}
}
I am overwriting the anyString() matcher for a specific argument. I am stubbing the download() method so that it returns a value for a specific argument and otherwise throws an IOException which gets handled by MyObject.readFiles.
The weird thing here is that the second stub (downloader.download("testFile")) throws the IOException set in the first stub (downloader.download(anyString())). I have validated that by throwing a different exception in my first stub.
Can someone explain me why the exception is thrown when adding an additional stub? I thought that creating a stub does not call the method/other stubs.
The problem is that when you write
when(downloader.download("testFile")).thenReturn("mockContent");
the first thing to be called is downloader.download, which you've already stubbed to throw an exception.
The solution is to use the slightly more versatile stubbing syntax that Mockito provides. This syntax has the advantage that it doesn't call the actual method when stubbing.
doThrow(IOException.class).when(downloader).download(anyString());
doReturn("mock content").when(downloader).download("test file");
I have listed other advantages of this second syntax, in my answer here
I thought that creating a stub does not call the method/other stubs.
This assumption is wrong, because stubbing is calling the mocks methods. Your test methods are still plain java!
Since stubbing for anyString will overwrite stubbing for any specific string you will either have to write two tests or stub for two specific arguments:
when(downloader.download("fail")).thenThrow(new IOException());
when(downloader.download("testFile")).thenReturn("mockContent");
Mockito is a very sophisticated piece of code that tries its best so that you can write
when(downloader.download(anyString())).thenThrow(new IOException());
which means “when the downloaders mock download method is called with anyString argument thenThrow an IOException” (i.e. it can be read from left to right).
However, since the code is still plain java, the call sequence actually is:
String s1 = anyString(); // 1
String s2 = downloader.download(s1); // 2
when(s2).thenThrow(new IOException()); // 3
Behind the scenes, Mockito needs to do this:
register an ArgumentMatcher for any String argument
register a method call download on the downloader mock where the argument is defined by the previously registered ArgumentMatcher
register an action for the previously registered method call on a mock
If you now call
... downloader.download("testFile") ...
the downloader mock checks whether there is an action register for "testFile" (there is, since there is already an action for any String) and accordingly throws the IOException.
Your 2nd mock statement is getting overriden by the first mock statement (because both mock statements are passing a String argument). If you want to cover try as well as catch back through your mock test then write 2 different test cases.

Is there a way of having something like jUnit Assert message argument in Mockito's verify method?

Let's assume a snippet of testing code:
Observable model = Class.forName(fullyQualifiedMethodName).newInstance();
Observer view = Mockito.mock(Observer.class);
model.addObserver(view);
for (Method method : Class.forName(fullyQualifiedMethodName).getDeclaredMethods())
{
method.invoke(model, composeParams(method));
model.notifyObservers();
Mockito.verify(
view, Mockito.atLeastOnce()
).update(Mockito.<Observable>any(), Mockito.<Object>any());
}
Mockito.verify method throws an exception if a method in a model hasn't invoked Observable.setChanged() method.
Problem: without adding loggers/System.print.out I can't realize what's the current method that has failed the test. Is there a way of having something similar to jUnit Assert methods:
Assert.assertEquals(
String.format("instances %s, %s should be equal", inst1, inst2),
inst1.getParam(),
inst2.getParam()
);
SOLUTION:
verify(observer, new VerificationMode()
{
#Override
public void verify(VerificationData data)
{
assertTrue(
format(
"method %s doesn't call Observable#setChanged() after changing the state of the model",
method.toString()
),
data.getAllInvocations().size() > 0);
}
}).update(Mockito.<Observable>any(), Mockito.<Object>any());
This question is ancient, but Mockito v2.1.0+ now has a built-in feature for this.
verify(mock, description("This will print on failure")).someMethod("some arg");
More examples included from #Lambart's comment below:
verify(mock, times(10).description("This will print if the method isn't called 10 times")).someMethod("some arg");
verify(mock, never().description("This will print if someMethod is ever called")).someMethod("some arg");
verify(mock, atLeastOnce().description("This will print if someMethod is never called with any argument")).someMethod(anyString());
This does the trick (simple and clear):
try {
verify(myMockedObject, times(1)).doSomthing();
} catch (MockitoAssertionError error) {
throw new MockitoAssertionError("Was expecting a call to myMockedObject.doSomthing but got ", error);
}
You cannot do in mockito.
Mockito syntax makes very easy to test expected behaviour, but it has no concept of test state.
What you're trying to do is to have some information that are not in the mocked object when the mocks fails expectations.
If you really want to do, I see 2 general ways:
either you create your own verificationMode implementing the interface
org.mockito.verification;
public static interface VerificationMode
and adding a method like atLeastOnceMsd(String msg) that will show the message in case of failing or adding the current tested method in the model to the view object
for example with a similar line in the inner loop.
view.setName("now we are testing " + method.getName());
There isn't a direct API call that allows a message on verify. But I think if you change your verify signature to use the method object rather than Mockito.any(), the toString() on the Method class will kick in and give you what you want.
Something like this.
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
import static org.mockito.Matchers.eq;
...
Observable model = Class.forName("class name").newInstance();
verify(view, times(1)).update(eq(model), anyObject());
for (Method method : Class.forName("class name").getDeclaredMethods())
{
method.invoke(model, composeParams(method));
model.notifyObservers();
verify(view, atLeastOnce()).update(eq(method), anyObject());
}
You could create matcher to print information on the current method. It's gonna be a bit clunky, but it will work print the method name when the verification fails.
There is org.mockito.internal.verification.Description which delegates to provided VerificationMode, but allows to override verification message.
you can use https://www.javadoc.io/doc/org.mockito/mockito-core/2.2.6/org/mockito/verification/VerificationMode.html#description(java.lang.String)
eg.
verify(mocked,times(1).description("This method is expected to invoke once")).someMethod();

Categories