Match generics with Mockito

10,189

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, not even in its any(Foo.class) variety (as of Mockito 1.x). 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.

(The "future major release" may come true for Mockito 2, where isA-style type checks may be introduced. ; See commentary from Mockito committer Brice.)

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.

Share:
10,189

Related videos on Youtube

Dario Zamuner
Author by

Dario Zamuner

New ideas pass through three periods: It can’t be done. It probably can be done, but it’s not worth doing. I knew it was a good idea all along! Arthur C. Clarke

Updated on September 14, 2022

Comments

  • Dario Zamuner
    Dario Zamuner over 1 year

    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?