18

I'm trying to mock the restTemplate.exchange method of Spring Rest.

In the same test I have multiple calls which differ only by the return type.

Here are the methods with the mocks I created

First

// Original method
restTemplate.exchange(UrlMap.SEARCH + '?' + searchDocsForm.toQueryParams(),
            HttpMethod.GET, null, new ParameterizedTypeReference<SearchResultsDTO<SolrDocumentDTO>>() {
            })
// Mock
when(restTemplate.exchange(any(String.class), any(HttpMethod.class), any(), Matchers.<ParameterizedTypeReference<SearchResultsDTO<SolrDocumentDTO>>>any())).thenReturn(
            new ResponseEntity<>(searchResultsDTO, HttpStatus.OK));

Second

// Original method
restTemplate.exchange(UrlMap.ALL_DOCUS_TOPICS,
            HttpMethod.GET, null, new ParameterizedTypeReference<List<SelectItem>>() {
            }).getBody();
// Mock
when(restTemplate.exchange(any(String.class), any(HttpMethod.class), any(), Matchers.<ParameterizedTypeReference<List<SelectItem>>>any())).thenReturn(
            new ResponseEntity<>(selectItems, HttpStatus.OK));

The generic parameters of ParameterizedTypeReference are not considered by the mock, and the last defined mock wins over the former.

Is there any way to make it work?

Dario Zamuner
  • 991
  • 2
  • 14
  • 28
  • There would be by creating a custom argument matcher. I know it is possible, however I have never done that so I can't help further, sorry :( (edit: [link](http://docs.mockito.googlecode.com/hg/latest/org/mockito/Matchers.html)) – fge Apr 16 '15 at 10:01
  • FYI, always replace `any(String.class)` with `anyString()`, and similarly for `anyInt()`, `anyFloat()`, etc... It's simpler, more concise, easire on the eyes, and not subject to type-erasure. – Kevin Welker Apr 22 '15 at 16:05

1 Answers1

31

Mockito isn't good at matching generics itself, but your solution is much easier than the general case.

Replace your:

Matchers.<ParameterizedTypeReference<SearchResultsDTO<SolrDocumentDTO>>>any())

with:

eq(new ParameterizedTypeReference<SearchResultsDTO<SolrDocumentDTO>>() {}))

First of all, Matchers.any() doesn't match type. In Mockito 1.x, any(Foo.class) doesn't even match type: any() matches all values, including null and including incorrect types.

Matches any object, including nulls

This method doesn't do type checks with the given parameter, it is only there to avoid casting in your code. This might however change (type checks could be added) in a future major release.

(Side note: In Mockito 2 and beyond, any(Foo.class) has English-like "any Foo" semantics, making it behave much more like isA(Foo.class).)

The generics are helpful to get the right parameter for exchange and thenReturn, but because of type erasure none of that type information makes it into the CLASS file, let alone the JVM. The only Matcher that asserts the type of its argument is isA, which takes a class literal and won't help you for parameterized types.

You could write a custom Matcher that inspects an argument's type and type parameters, if they aren't subject to erasure, but for your specific case that's not necessary.

Type erasure is the whole reason ParameterizedTypeReference exists: It captures the generics information into a subclass, where the parameterized type will not be erased. This same pattern is used for TypeToken in Guava or TypeLiteral in Guice. All of these implementations describe a parameterized type as an instance.

Importantly, all of them—including ParameterizedTypeReference—support equals and hashCode, so new ParameterizedTypeReference<A<B>>(){} equals new ParameterizedTypeReference<A<B>>(){} even though the instances are different. (See the code here.)

Because references to the same parameterized type are equal, use Mockito's eq matcher with a different reference, and things should be fine.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251