Reputation:
I have a simple POJO that contains a Map<String,Widget>
property:
private Map<String, Widget> widgetCache = new HashMap<String, Widget>();
@Override
public Logger newWidget(String name, Widget widget) {
// First, print the contents of widgetCache in pretty print format.
Map<String, Widget> map = widgetCache;
List<String> keys = new ArrayList<String>(map.keySet());
System.out.println("Printing..." + keys.size());
for (String key: keys)
System.out.println(key + ": " + map.get(key).getName());
if(!widgetCache.containsKey(name)) {
System.err.println("I don't contain " + name);
widgetCache.put(name, widget);
}
return widgetCache.get(name);
}
Simple enough: it just doesn't allow duplicate Widget
s from being inserted into the map. When I go to test this in a JUnit test method using Mockito (1.9.5):
CachingWidgetFactory fixture = new CachingWidgetFactory();
// By the way, I get errors if I try to make this next line:
// HashMap<String,Widget> mockMap = Mockito.mock(HashMap<String,Widget>.class);
// How do I enforce generics here when defining a mock?
HashMap mockMap = Mockito.mock(HashMap.class);
fixture.setLoggerCache(mockMap);
fixture.newWidget("Widget-A", new Widget("Widget-A"));
fixture.newWidget("Widget-A", new Widget("Widget-A"));
Mockito.verify(mockMap, Mockito.times(1))
.put(Mockito.anyString(), Mockito.<Logger>any());
I get the test failing with the following JUnit output:
org.mockito.exceptions.verification.TooManyActualInvocations:
hashMap.put(<any>, <any>);
Wanted 1 time:
And in the (STDOUT) console output, I see this:
Printing...0
I don't contain Widget-A
Printing...0
I don't contain Widget-A
So it looks like the mock that Mockito is returning is allowing the 2nd (duplicate) insert. However, when I remove the Mockito mockMap
entirely, and make the test method look like:
CachingWidgetFactory fixture = new CachingWidgetFactory();
fixture.newWidget("Widget-A", new Widget("Widget-A"));
fixture.newWidget("Widget-A", new Widget("Widget-A"));
Then in the console output, I get:
Printing...0
I don't contain Widget-A
Printing...1
Now the (non-mocked) code is correctly preventing the duplicate insert. So it's almost as if Mockito is returning a new HashMap
every time newWidget
is being called. What's going on here, and why? (And bonus points if you can help me on the generic issue mentioned above.) Thanks in advance.
Upvotes: 3
Views: 6930
Reputation: 10262
// How do I enforce generics here when defining a mock?
HashMap mockMap = Mockito.mock(HashMap.class);
Handling generics is much easier if you use the mockito annotations, e.g.
@Mock
HashMap<String, Widget> mockMap;
and then in your setup method:
MockitoAnnotations.init(this);
But to your problem, just use
HashMap<String, Widget> map=new HashMap<String, Widget>();
so that the test uses a real HashMap that behaves like one, since that is the thing you want to test. If you're going to mock all the HashMap operations yourself, chances are you are making some kind of mistake (like forgetting to mock a method).
Upvotes: 0
Reputation: 2233
That's a common mistake. You have to remember that you mocked HashMap
. So, mockito is not giving you a new map everytime, it just don't know how to behave because you mocked the HashMap.
Giving that, it will behave as you tell it to behave. If you haven't said anything, it will return default values/do nothing when you call your methods. So, at the line
if (!widgetCache.containsKey(name))
because you didnt say how it should behave, it will return the default value of false
. You can mock the map to return false at the second call using Mockito with something like
given(hashMap.containsKey(name)).willReturn(false, true);
With that, HashMap will return that "contains" the key at the second call of containsKey
with the given name. You can read its documentation here
Another thing you could do is to give to it a real implementation of HashMap, but I prefer the "mock" way :)
EDIT
I gave the link to BDDMockito, but it works the same way with when
->thenReturn
. It's just syntax sugar :) I find it easier to read.
Upvotes: 2