20

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?

2
  • 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) Commented Apr 16, 2015 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. Commented Apr 22, 2015 at 16:05

1 Answer 1

32

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.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.