Is it good practice to put assertions inside `Mockito.argThat`?

In a JUnit test using Mockito, I have an argThat matcher like this:

Mockito.argThat(obj -> {
  return "ok".equals(obj.getStuff()) ||
         "ok2".equals(obj.getStuff2()) ||
         "ok3".equals(obj.getStuff3()) ||
         "ok4".equals(obj.getStuff4());
});

When the test fails, it generically fails and it’s hard to figure out which condition caused the failure. I tried adding assertions inside argThat for better clarity:

Mockito.argThat(obj -> {
  Assert.assertEquals("ok", obj.getStuff());
  Assert.assertEquals("ok2", obj.getStuff2());
  Assert.assertEquals("ok3", obj.getStuff3());
  Assert.assertEquals("ok4", obj.getStuff4());
  return true;
});

This gives clearer failure messages, but it feels a bit “wonky.” Is there a better way to handle this while using mockito argThat?

Putting assertions inside argThat is generally not recommended. argThat is meant for matching arguments, not performing test assertions.

A cleaner approach is to return a boolean from your matcher and then verify with separate assertions afterward:

Mockito.verify(mock).someMethod(Mockito.argThat(obj ->
    "ok".equals(obj.getStuff()) &&
    "ok2".equals(obj.getStuff2()) &&
    "ok3".equals(obj.getStuff3()) &&
    "ok4".equals(obj.getStuff4())
));

MyObject captured = argumentCaptor.getValue();
assertEquals("ok", captured.getStuff());
assertEquals("ok2", captured.getStuff2());
assertEquals("ok3", captured.getStuff3());
assertEquals("ok4", captured.getStuff4());

This way, failure messages are clear, and you’re not misusing argThat.

Another approach is to define a custom ArgumentMatcher. This gives better readability and more detailed failure messages without putting assertions inside the lambda:

class MyObjectMatcher implements ArgumentMatcher<MyObject> {
    @Override
    public boolean matches(MyObject obj) {
        return "ok".equals(obj.getStuff()) &&
               "ok2".equals(obj.getStuff2()) &&
               "ok3".equals(obj.getStuff3()) &&
               "ok4".equals(obj.getStuff4());
    }

    @Override
    public String toString() {
        return "Expected MyObject with stuff='ok', stuff2='ok2', stuff3='ok3', stuff4='ok4'";
    }
}

Mockito.verify(mock).someMethod(Mockito.argThat(new MyObjectMatcher()));

The toString() method helps make failure messages more descriptive when the matcher fails.

Instead of putting assertions inside argThat, you can capture the argument and assert afterward. This often produces the clearest test reports:

ArgumentCaptor<MyObject> captor = ArgumentCaptor.forClass(MyObject.class);
Mockito.verify(mock).someMethod(captor.capture());

MyObject obj = captor.getValue();
assertEquals("ok", obj.getStuff());
assertEquals("ok2", obj.getStuff2());
assertEquals("ok3", obj.getStuff3());
assertEquals("ok4", obj.getStuff4());

I like this approach because each failed assertion clearly indicates which field caused the failure, and it keeps Mockito usage idiomatic.