Mockito, argThat, and hasEntry

tl;dr: These tests don't compile because the type parameters don't match. What changes should I make to make them compile and run correctly?

I have some non-test code that calls into a service. It calls the service's activate method with a map parameter.

public class Foo {
  private final Service service;

  public Foo(Service service) {
    this.service = service;
  }

  public void bar() {
    Map<String, ?> params = getParams();
    service.activate(params);
  }

  private Map<String, ?> getParams() {
    // something interesting goes here
  }
}

Some code I'm trying to test has a dependency on a service like this one:

public interface Service {
  public void activate(Map<String, ?> params);
}

I'd like to test this code by mocking the service with Mockito and verifying that activate was called with a reasonable map. The following code works:

@Test
public void testExactMap() {
  Service mockService = mock(Service.class);
  Foo foo = new Foo(mockService);

  foo.bar();

  Map<String, String> expectedParams = new HashMap<>();
  expectedParams.put("paramName", "paramValue");
  verify(service).activate(expectedParams);
}

However, I'd like to just test that the map contains one particular entry. The Hamcrest hasEntry matcher seems perfect for this use case:

@Test
public void testHasEntry() {
    Service mockService = mock(Service.class);
    Foo foo = new Foo(mockService);

    foo.bar();

    verify(mockService).activate(argThat(hasEntry("paramName", "paramValue")));
}

When I try this, I get the following error in IntelliJ IDEA:

Error:(31, 45) java: incompatible types: inference variable T has incompatible bounds
    equality constraints: java.util.Map<? extends java.lang.String,? extends java.lang.String>
    upper bounds: java.util.Map<java.lang.String,?>,java.lang.Object

The problem here is that I need a Mockito matcher of Map<String, ?>, but hasEntry gives me a matcher of Map<? extends String, ? extends String>. Even with explicit type parameters, I can't figure out what to do to reconcile the "? extends" part of the type parameter. What should I do to resolve this error? Is there a specific cast or explicit type parameter I should use?

I understand that I can use ArgumentCaptor for this. Is that really the only way to do this? Is this possible at all with Hamcrest matchers?

1 Answer

The argThat return type is not being inferred for some reason. Try explicitly casting as shown below:

 Mockito.verify(foo).bar((Map<String, String>) argThat(Matchers.hasEntry("paramName", "paramValue")));

The testHasEntryCast() can be fixed as shown below. Notice that cast (Map<String, ?>) is to argThat return type:

@Test
public void testHasEntryCast() {
    Service mockService = mock(Service.class);
    Foo foo = new Foo(mockService);

    foo.bar();

    verify(mockService).activate((Map<String, ?>)  argThat(hasEntry("paramName", "paramValue")));
}
3

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Sarah Jenkins

Sarah Jenkins

Senior Technology Editor & AI Specialist

Sarah Jenkins is a veteran tech journalist with over 12 years of experience covering artificial intelligence, mobile innovations, and digital ethics. Her insights have appeared in leading technology publications worldwide.

Share this article
Twitter Facebook Pinterest