In Mockito, you can use argument matchers to make your tests more flexible by allowing you to stub or verify methods with generic or specific arguments. However, combining argument matchers with custom matchers requires some understanding of how Mockito handles these matchers.
Using Argument Matchers
Mockito provides a variety of built-in argument matchers, such as `any()`, `anyInt()`, `anyString()`, and `eq()`, which are used to match arguments in a generic or specific way. These matchers are defined in the `org.mockito.ArgumentMatchers` class and are typically used with the `when()` method for stubbing or the `verify()` method for verification.
For example, you can use `any()` to match any object, including null, or `eq()` to match a specific value:
java
when(mockFoo.bool(anyString(), anyInt(), any(Object.class))).thenReturn(true);
when(mockFoo.bool(eq("false"), anyInt(), any(Object.class))).thenReturn(false);
Custom Argument Matchers
Custom argument matchers allow you to define your own logic for matching arguments. These matchers implement the `ArgumentMatcher` interface and can be used with the `argThat()` method. However, they do not directly support using Mockito's built-in matchers like `any()` or `eq()` within their implementation.
To create a custom matcher that supports fuzzy matching similar to Mockito's built-in matchers, you can design it to accept parameters that are themselves matchers. However, this is not straightforward with Mockito's matchers because they work through side effects and are designed to be used directly in `when()` or `verify()` calls.
Example of a Custom Matcher
If you want to match a custom object with specific properties, you might create a custom matcher like this:
java
public class MyMatcher extends ArgumentMatcher {
private String expectedName;
private int expectedAge;
public MyMatcher(String expectedName, int expectedAge) {
this.expectedName = expectedName;
this.expectedAge = expectedAge;
}
@Override
public boolean matches(CustomObject argument) {
return argument.getName().equals(expectedName) && argument.getAge() == expectedAge;
}
}
You can use this matcher with `argThat()`:
java
when(mock.doSomething(argThat(new MyMatcher("John", 30)))).thenReturn("success");
Using Hamcrest Matchers
If you need more flexibility, such as combining matchers, you might consider using Hamcrest matchers, which are more versatile and can be used with Mockito through the `MockitoHamcrest` class. Hamcrest matchers can be combined using logical operators like `and()`, `or()`, and `not()`.
Limitations with Mockito's Built-in Matchers
Mockito's built-in matchers like `any()` and `eq()` cannot be directly used within a custom matcher's implementation because they are designed to work through side effects. This means you cannot pass `anyInt()` or `eq("value")` directly to a custom matcher and expect it to work seamlessly.
However, you can design your custom matcher to accept parameters that mimic the behavior of these matchers. For example, you could create a custom matcher that accepts a Hamcrest matcher for one of its properties.
Conclusion
While you can use custom matchers with Mockito, combining them with built-in argument matchers like `any()` or `eq()` requires careful design. For more complex matching logic, using Hamcrest matchers might provide the flexibility you need.
Citations:[1] https://www.digitalocean.com/community/tutorials/mockito-argument-matchers-any-eq
[2] https://mockk.io
[3] https://www.softwaretestinghelp.com/mockito-matchers/
[4] https://stackoverflow.com/questions/56559146/junit-using-eq-argument-matcher-vs-passing-string-directly/56572275
[5] https://groups.google.com/g/mockito/c/LJyOAJYR-54
[6] https://stackoverflow.com/questions/16458136/mockito-invalid-use-of-argument-matchers
[7] https://stackoverflow.com/questions/39398513/how-can-i-create-a-custom-argumentmatcher-that-accepts-arguments-from-other-matc
[8] https://www.scalatest.org/user_guide/using_matchers
[9] https://www.youtube.com/watch?v=_-EKS7kkeKo