Mockito: How to match a String and List<String> arguments - java

I'm basically trying to verify whether the correct parameters are being invoked in a method.
Here's a snippet of the code I'm trying to test:
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.in("type", Arrays.asList("employee", "supervisor");
Verifying this using:
Mockito.verify(mockSession).createCriteria(User.class);
Mockito.verify(mockCriteria).add(Restrictions.in("type", Arrays.asList("employee", "supervisor"));
The first verify statement works. The second doesn't because, I believe, that the JVM detects two different List objects being compared to. However, when I change the second verify statement to:
Mockito.verify(mockCriteria).add(Restrictions.in("type", Mockito.anyList());
It works like a charm. However, I do want to ensure that the two Strings, employee and supervisor, are inside the List and that won't happen by using Mockito.anyList().
How do I get this to work?
EDIT: Please take note that I do not wish to only verify whether a List has been passed. I want to ensure that the correct Strings are passed inside that List as well

Unfortunately, here you cannot easily check what you want to check with matchers.
Mockito matchers work via side-effects, where a call to a matcher tells Mockito to use the matcher rather than testing equality. That means that Mockito matchers don't work at all when nested within objects like Criterion.
verify(mockCriteria).add(
Restrictions.in("type", Arrays.asList("employee", "supervisor"));
In the above, you don't use matchers, you're verifying that mockCriteria.add is called with an object that equals the Criterion you specify. However, if the returned Criterion doesn't override equals (and hashCode) then it will only test that the instances are the same—which will never be true here, because you're creating a new one in the verify statement.
verify(mockCriteria).add(Restrictions.in("type", anyList()));
Here, it looks like you're verifying that mockCriteria.add is called with any list, but anyList() actually tells Mockito to skip checking for one parameter and returns the dummy value null. You then create a Criterion where "type" in null, and then Mockito sees one any matcher on the stack for a one-argument method call, discards the newly-created invalid Criterion, and just checks that add was called at all. It looks like everything's working, but your mockCriteria could receive literally any parameter including null and the test would still pass. (If you were using a second matcher, or if add took two parameters, you would get InvalidUseOfMatchersException instead of your false positive.)
To make this work with Mockito matchers, you would need to write your own Hamcrest matcher that matches the entire Criterion, and then use argThat to let Mockito match arguments with it.
As Andy Turner mentioned, one way to solve this is to use ArgumentCaptor:
ArgumentCaptor<Criterion> captor = ArgumentCaptor.forClass(Criterion.class);
verify(mockCriteria).add(captor.capture());
Criterion criterion = captor.getValue();
// assert against criterion
However, be warned that this may be of limited use: Criterion doesn't have a lot of properties you can inspect. You may need to use toString(), as in this SO question.

Related

How should I mock Arrays.asList in Java Unit Test?

I have a Unit Test in a Java app and I get error while executing the following line:
when(Arrays.asList(Locale.getISOCountries()).contains(countryCode)).thenReturn(true);
The error message does not mention mocking, but I think the problem is related to not mocking
Arrays.asList(Locale.getISOCountries() part in the when method. So, how should I treat this line? Normally I mock services and repositoories, but ı am not sure if I should mock this part like #Mock Locale locale;. I tried it but not worked.
Here is the error message:
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
Boolean cannot be returned by toString()
toString() should return String
Note: I also tried doReturn(true).when(Arrays.asList(Locale.getISOCountries()).contains(countryCode)); but not worked.
TL;DR: don't try to mock this. For one thing, you can't; for another, you shouldn't.
It is best to use the default behavior. Remove the line where you attempt to configure this behavior with Mockito.
If you insist that you need to be able to use an arbitrary list of countries, see the section on "dependency injection" below, noting the caveat mentioned.
Attempting to mock the return value of Arrays.asList(Locale.getISOCountries()).contains(countryCode) would specifically mean that you are trying to alter the behavior of the List<String> returned by Arrays.asList when given a particular String[] as an argument.
You can't with Mockito, because it isn't magic: it doesn't allow replacement of behavior for arbitrary expressions.
The way something like when(aMock.method(123)).thenReturn("hello") works is that the mock object - aMock - records that method was invoked with an argument 123. This information is pushed onto a stack, from which the when method is able to retrieve it and deal with it.
The pushing-onto-the-stack is only done if aMock is an object that Mockito has created: Mockito implements the methods of an interface/overrides the methods of a class to do this recording.
None of the objects involved in Arrays.asList(Locale.getISOCountries()).contains("GB") were created by Mockito. As such, none of these objects have methods which capture the invocation and push it onto the stack; Mockito basically can't see that anything is going on, so when the when call comes, it's just using whatever state is hanging around in Mockito. The error message shown:
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
Boolean cannot be returned by toString()
toString() should return String
indicates that it thinks you are configuring the behavior of a toString method, nothing obviously to do with Arrays.asList, Locale.getISOCountries() or List.contains.
For the same reason, you can't mock the return value of Arrays.asList(Locale.getISOCountries()) or Locale.getISOCountries(): they're just not objects Mockito knows anything about.
So, that's why this mocking doesn't work. On to why you don't want to do it in the first place:
List.contains has specific semantics, namely that it returns true if and only if the argument is in the list. There are implications of this, such as aList.contains(o) == true implying that a call to aList.indexOf(o) would have returned a non-negative value.
This means that the consequences of mocking contains would be either:
You have to also configure the mocking of other methods on the list such that the behavior of the list is consistent with that result of List.contains (so, indexOf, subList, iterator etc) - and, how the mock should behave if you were to set the element equal to o to something else (because Arrays.asList allows that);
or
You don't configure the other mocking, and your List doesn't have behavior consistent with an actual List, which will have unpredictable effects on the behavior of your code.
But you don't actually have to worry about doing 1. (which is good, because it would be essentially impossible to do correctly, e.g. such that copies of the list have the same contains behavior) or 2. (which is good, because introducing the unpredictability of a broken List is simply a bad idea): Arrays.asList has a perfectly-working implementation of the List interface; all you need to make sure is that the argument you pass in (in this case Locale.getISOCountries()) contains the element that you want.
Mocking of the return value of Arrays.asList(...).contains is neither necessary nor desirable.
So, the problem now is shifted from one of ensuring that Arrays.asList(Locale.getISOCountries()).contains(countryCode) to one of ensuring that Locale.getISOCountries() has at least one element that is equal to countryCode.
As stated in the Javadoc of Locale.getISOCountries():
Returns a list of all 2-letter country codes defined in ISO 3166
Given that GB (as was originally asked) is a 2-letter country code defined in ISO 3166, it will always (or, at least, until ISO3166 is changed) be an element of the array returned by Locale.getISOCountries().
Hence, the array returned by Locale.getISOCountries() is going to contain "GB", so Arrays.asList(Locale.getISOCountries()).contains("GB"), without actually doing anything.
But then there is the question of making Arrays.asList(Locale.getISOCountries()).contains(countryCode) true, for an arbitrary countryCode. By the argument above, you want to effect this only by ensuring that Locale.getISOCountries() has an element equal to countryCode.
If countryCode is another two-letter ISO-3166-2 country code, this already works, as in the GB case.
If countryCode is not an ISO country code, you absolutely should not want to make a method return it if that method is documented to return only ISO-3166-2 country codes, because this wouldn't happen in production code.
Mocking should not be used as a way to do arbitrary things in tests.
You only ever want a test to test things which can actually happen in production code. Ideally, you use "the real thing"; test doubles (of which a mock is one type) come into play only if using "the real thing" is hard because the real thing is slow, expensive, difficult to reproduce (e.g. an error condition like a network error or full disk) etc. But, the testing double should only be doing things you'd see for real.
So, even if you could mock Locale.getISOCountries() to ensure that it returned an array containing a non-ISO-3166-2 countryCode, you shouldn't, because this will never actually happen in production; and a test that tests something that cannot happen in production has very limited value in telling you something useful.
Actually, you can mock static methods like Locale.getISOCountries() using PowerMock; but changing the behavior of a static method is highly inadvisable, because it doesn't just change the behavior for you - it changes it for anybody who calls it. So, there could be unintended consequences in the behavior, both nearby and in the rest of the code.
For example:
when(Arrays.asList(Locale.getISOCountries())).thenReturn(Collections.singletonList(countryCode.toUpperCase(Locale.ENGLISH)));
Aside from changing the mutability semantics of the returned list (Arrays.asList allows set; Collections.singletonList doesn't), it is now inconsistent with other country-code-returning methods in the Locale class (e.g. getISOCountryCodes(Type). Chasing down and fixing all such inconsistencies is nigh-on impossible.
What if we could use PowerMock to mock the return value of Arrays.asList(Locale.getISOCountries()), i.e. a less generic use-case of the Locale.getISOCountries() method? This still suffers from the problems of unintended consequences - there could be other parts of the program which invoke Arrays.asList(Locale.getISOCountries()) where the mocked behavior isn't desirable.
What if we could use PowerMock to mock the return value of just one specific call to Arrays.asList(Locale.getISOCountries())? That's brittle, for example, if another call is added, you'd have to make sure the test is correctly updated, otherwise the behavior would be inconsistent between the calls.
There isn't a good way to win the PowerMock battle.
There are an awful lot of words here, but the key point is that there are really rather difficult-to-deal-with consequences of trying to use mocking inappropriately: using the actual behavior (without mocks) is best; but, if a mock has to be used, it should not behave in a way that the real code never will.
Fortunately, Mockito is stopping you from doing this; but hopefully this answer has given a thorough explanation as to why it was the wrong approach in the first place.
Dependency injection
With all of this said, there is a way to make your code work in the face of arbitrary country codes: dependency injection.
Whilst there are lots of DI frameworks (e.g. Guice, Spring) which introduce a lot of power (and complexity and horror), dependency injection simply means: passing things as arguments.
If, for example, the code in which you want Arrays.asList(Locale.getISOCountries()).contains(countryCode) to be true occurs in a method, inject the country list as a parameter to that method:
class MyClass {
void myMethod(List<String> countryCodes, String countryCode) {
if (countryCodes.contains(countryCode)) {
// ...
}
}
}
or make it a constructor parameter:
class MyClass {
private final List<String> countryCodes;
MyClass(List<String> countryCodes) {
// Defensive copy.
this.countryCodes = Collections.unmodifiableList(new ArrayList<>(countryCodes));
}
void myMethod(String countryCode) {
if (countryCodes.contains(countryCode)) {
// ...
}
}
}
In your production code, pass in Arrays.asList(Locale.getISOCountries()); in test code, pass in whatever list you like.
But still: beware of the interaction between this code and code which uses Locale.getISOCountries() and allied methods directly in tests. If there is a risk of such an interaction, it remains safer to write your tests using the static Local.getISOCountries().

Use of chaining OngoingStubbing Mockito?

In Mockito when we try to mock a method call, let's say we configure something like this
when(exampleClass.getOutputString(anyString())).thenReturn("output1");
This is all understandable. But my question is what is the reason this thenReturn("output1") method returns an OngoingStrubbing object (same as what when(exampleClass.getOutputString(anyString())) method returns), so that we can do something like this
when(exampleClass.getOutputString(anyString())).thenReturn("output1").thenReturn("output2");
or
when(exampleClass.getOutputString(anyString())).thenReturn("output1").thenThrow(new IllegalArgumentException());
However, both the above cases, when using the mock, it only returns "output1" and that's all the story. Anyone knows, why this chaining feature is present, and what are the uses of it? Kind of same thing applies to doReturn() too.
This means for the first call gives ouput1 and in the second call gives output2, and so on.
when(exampleClass.getOutputString(anyString())).thenReturn("output1").thenReturn("output2");
, The second one you need to reach one condition in the target test class so gives
output1 for the first case but in the second one you want to fail to test for example
try-catch clause or see if the code cover the broken scenarios like exceptions
when(exampleClass.getOutputString(anyString())).thenReturn("output1").thenThrow(new IllegalArgumentException());

Stubing/Verifying a call with a FunctionalInterface/MethodReference argument

I never had to stub a call that accepts a functional interface/method reference as argument, so I just find out about the obvious incapacity for compare with "equals" these, aside from the reference comparison.
So how does one deal with this scenario during testing? Do we have to use any() either we want it or not?
Comparator<String> comparator = Comparator.naturalOrder();
when(myColaborator.isCalledWith(comparator)).thenReturn("foo"); //This is not gonna work as long as the real call does not use theat very same reference
It is necessary to verify its collaborator whether has recieved an exactly Comparator in a simple test case since such a communication protocol test in a book of GOOS describes whether they will work together correctly. and then skip checking the rest of tests by using any(Comparator.class). you can read it further in Martin's blog:
in the second test case is that I've relaxed the constraints on the expectation by using withAnyArguments. The reason for this is that the first test checks that the number is passed to the warehouse, so the second test need not repeat that element of the test. If the logic of the order needs to be changed later, then only one test will fail, easing the effort of migrating the tests. As it turns out I could have left withAnyArguments out entirely, as that is the default.

Mockito behaviors called incorrectly

I have some Mockito behaviors defined and I also have verboseLogging turned on trying to debug why my test is failing. I see in the log that some of the methods are being called with either empty or null parameters, which I am not doing.
DataTypeService dataTypeService = Mockito.mock(DataTypeService.class, withSettings().verboseLogging());
when(dataTypeService.isMultivalued(anyString())).thenReturn(true);
I see this in the log:
dataTypeService.isMultivalued("");
invoked: -> at com.example.rest.service.api.v3.impl.ContentServiceImplTest.getDocumentBySchemaMultiValueTest(ContentServiceImplTest.java:186)
has returned: "false" (java.lang.Boolean)
I see something similar in the log for all of my other when statements as well. They return null instead of what I have in the thenReturn and then it continues and returns the proper item on a second call; however, I am only calling it once. Does defining a when statement, execute it under the covers with no params?
Does defining a when statement, execute it under the covers with no
params?
Kind of. When you use anyString() Mockito puts some internal state and then returns a default value (as stated in the anyString() Javadoc).
After doing that, it calls the mocked object with the default value returned by the Matcher, recording its calling internally to associate with a posterior call to thenReturn or thenThrow. But a mock with no recorded state yet will return a default answer, which is false in your case. So when you verboseLogging() it logs the recording call, printing the first false. But, after the recording, your mock is good to go, returning the expected value in the second and subsequent calls. Therefore, what you're seeing is just Mockito being Mockito, nothing wrong with that. :)

Multiple thenReturn not working properly in the Mockito

I'm testing a method that call same method (db.getData()) twice. But I must return two different values.
Mockito.when(db.someMethod()).thenReturn(valueOne).thenReturn(valueTwo);
Then I tried out multiple thenReturn().
Unfortunately I'm getting only valueTwo for first & second db.getData() method call.
You are not showing a lot of context but here are some ideas:
make sure db is really a mock object
use the debugger to check if db.someMethod() is called twice as you expect
You can also use thenReturn(valueOne, valueTwo); although that should not make a difference
I suspect that your method is called more than twice and that you are missing the first invocation (which returns valueOne) and only looking at subsequent invocations (which will all return valueTwo).
See the API:
//you can set different behavior for consecutive method calls.
//Last stubbing (e.g: thenReturn("foo")) determines the behavior of further consecutive calls.
when(mock.someMethod("some arg"))
.thenThrow(new RuntimeException())
.thenReturn("foo");
It's probably you are debugging and when you want to fetch the data of the breakPoint line, you are fetching from mock, so it will return one of it's thenReturn() parameters, so when you resume the test, it will test it with the second parameter.
I recommend you if you doubt it's working properly, one time fetch all thenReturn() items and after that you agree they're OK, start the test again with no tracing thenReturn() items.

Categories