Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

https://github.com/wesleym/matchertest

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?

like image 839
Wesley Avatar asked Apr 07 '17 23:04

Wesley


1 Answers

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")));
}
like image 190
VinPro Avatar answered Oct 19 '22 13:10

VinPro