Albert Lacambra BasilAlbert Lacambra Basil

When testing and mocking, sometime we perform some assertions/verifications that are not covered by the standard Mockito or Hamcrest Matchers.

In these cases, we can create our own matchers. Your own matchers, can increase readability, perform more complex assertions or call verifications.

Let’s see an example.

In the following code we have an Article class. Our article is quite simple. It has an id, a title and a text.

public class Article {
  private int id;
  private String title;
  private String text;

  public Article(int id, String title, String text) {
    this.id = id;
    this.title = title;
    this.text = text;
  }

  public int getId() {
    return id;
  }

  public String getTitle() {
    return title;
  }

  public String getText() {
    return text;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Article article = (Article) o;
    return id == article.id;
  }

  @Override
  public int hashCode() {
    return Objects.hash(id);
  }
}

We can store this article in a store using the field id as its primary key. We use its methods equals and hashcode to identify two instances that represents the same stored article.

public class ArticleStore {

  private Map<Integer, Article> articles;
  private DuplicationService duplicationService;

  public ArticleStore(DuplicationService duplicationService) {
    this.duplicationService = duplicationService;
    articles = new HashMap<>();
  }

  public Article createArticle(String text, String title) {
    Article article = new Article(getNextId(), text, title);
    storeArticle(article);
    return article;
  }

  public Article createCopy(int id) {
    Article article = articles.get(id);
    Article copy = duplicationService.duplicate(getNextId(), article);
    storeArticle(copy);

    return copy;
  }

  public List<Article> getArticlesLike(Article article) {
    return articles.values()
        .stream()
        .filter(a -> a.getText().equalsIgnoreCase(article.getText()) && a.getTitle().equalsIgnoreCase(article.getTitle()))
        .collect(Collectors.toList());
  }

  void storeArticle(Article article) {
    articles.put(article.getId(), article);
  }

  private Integer getNextId() {
    return articles.size() + 1;
  }
}

We can test that an article has been created checking that the storeArticle method has been called with the desired article.

@Test
void createArticle() {
  ArticleStore articleStore = Mockito.spy(ArticleStore.class);
  articleStore.createArticle("someText", "title");

  Article article = new Article(1, "someText", "title");

  Mockito.verify(articleStore).storeArticle(Mockito.eq(article));

  //Or simply
  Mockito.verify(articleStore).storeArticle(article);
}

Now, we have a service that duplicates this articles. That means that the duplicate service will create a new article that contains a different id but the same title and text.

package tech.lacambla.blog.examples.matchers;

public class DuplicationService {

  public Article duplicate(int nextId, Article article) {
    return new Article(nextId, article.getTitle(), article.getText());
  }

}

If we want to verify that this method is correctly called, we must create a matcher, ArgumentMatcher.isDuplicateOf(Article article).

class ArticleMatcher implements ArgumentMatcher<Article> {


  public final Article article;


  public static Article isDuplicateOf(Article article) {

   // Register our matcher.

    mockingProgress().getArgumentMatcherStorage().reportMatcher(new ArticleMatcher(article));
    return null;
  }

  public ArticleMatcher(Article article) {
    this.article = article;
  }

  /**
   * Implements matches method with our matching logic.
   * @param article
   * @return
   */
  @Override
  public boolean matches(Article article) {
    return this.article.getText().equalsIgnoreCase(article.getText());
  }

  public String toString() {
    return "<ArticleMatcher>";
  }
}

Now we can use our ArgumentMatcher to create stubs and verify calls:

@Test
  void duplicateArticle() {

    Article article = articleStore.createArticle("someText", "title");
    articleStore.createCopy(article.getId());

    //2 times since the both articles have the same contents
    Mockito.verify(articleStore, times(2)).storeArticle(ArticleMatcher.isDuplicateOf(article));
  }