Hamcrest Custom Matchers

Hamcrest Custom Matchers

1. Introdução

Bem como matchers integrados,Hamcrest also provides support for creating custom matchers.

Neste tutorial, veremos mais de perto como criá-los e usá-los. Para obter uma prévia dos matchers disponíveis, consultethis article.

2. Configuração de Matchers personalizados

Para obter o Hamcrest, precisamosadd the following Maven dependency to our pom.xml:


    org.hamcrest
    java-hamcrest
    2.0.0.0
    test

A última versão do Hamcrest pode ser encontrada emMaven Central.

3. ApresentandoTypeSafeMatcher

Antes de começar com nossos exemplos,it’s important to understand the class TypeSafeMatcher.We’ll have to extend this class to create a matcher of our own.

TypeSafeMatcher é uma classe abstrata, portanto, todas as subclasses devem implementar os seguintes métodos:

  • matchesSafely(T t): contém nossa lógica de correspondência

  • describeTo(Description description): personaliza a mensagem que o cliente receberá quando nossa lógica de correspondência não for cumprida

Como podemos ver no primeiro método,TypeSafeMatcher is parametrized, so we’ll have to declare a type when we use it. Esse será o tipo de objeto que estamos testando.

Vamos deixar isso mais claro observando nosso primeiro exemplo na próxima seção.

4. Criando o MatcheronlyDigits

Para nosso primeiro caso de uso,we’ll create a matcher that returns true if a certain String contains only digits.

Portanto,onlyDigits aplicado a “123” deve retornartrue enquanto “hello1” e “bye” deve retornar falso.

Vamos começar!

4.1. Criação de Matcher

Para começar com nosso matcher, vamos criar uma classe que estendeTypeSafeMatcher:

public class IsOnlyDigits extends TypeSafeMatcher {

    @Override
    protected boolean matchesSafely(String s) {
        // ...
    }

    @Override
    public void describeTo(Description description) {
        // ...
    }
}

Observe que, como o objeto que testaremos é um texto, estamos parametrizando nossa subclasse deTypeSafeMatcher com a classeString.

Agora estamos prontos para adicionar nossa implementação:

public class IsOnlyDigits extends TypeSafeMatcher {

    @Override
    protected boolean matchesSafely(String s) {
        try {
            Integer.parseInt(s);
            return true;
        } catch (NumberFormatException nfe){
            return false;
        }
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("only digits");
    }
}

Como podemos ver,matchesSafey está tentando analisar nossa entradaString em umInteger. Se for bem-sucedido, ele retornarátrue. Se falhar, retornafalse. Ele responde com sucesso ao nosso caso de uso.

Por outro lado,describeTo está anexando um texto que representa nossas expectativas. Veremos como isso mostra a seguir, quando usarmos nosso combinador.

We only need one more thing to complete our matcher: a static method to access it, portanto, ele se comporta como o restante dos matchers integrados.

Então, vamos adicionar algo como isto:

public static Matcher onlyDigits() {
    return new IsOnlyDigits();
}

E pronto! Vamos ver como usar este matcher na próxima seção.

4.2. Uso do Matcher

Parause our brand new matcher, we’ll create a test:

@Test
public void givenAString_whenIsOnlyDigits_thenCorrect() {
    String digits = "1234";

    assertThat(digits, onlyDigits());
}

E é isso. Este teste será aprovado porque a entradaString contém apenas dígitos. Lembre-se de que, para torná-lo um pouco mais legível,we can use the matcher is that acts as a wrapper over any other matcher:

assertThat(digits, is(onlyDigits()));

Por fim, se executássemos o mesmo teste, mas com a entrada "123ABC", a mensagem de saída seria:

java.lang.AssertionError:
Expected: only digits
     but: was "123ABC"

This is where we see the text that we appended to the describeTo method. Como podemos ter notado,it’s important to create a proper description of what’s expected in the test.

5. divisibleBy

Então, e se quiséssemos criar um correspondente que define se um número é divisível por outro número? Para esse cenário,we’ll have to store one of the parameters somewhere.

Vamos ver como podemos fazer isso:

public class IsDivisibleBy extends TypeSafeMatcher {

    private Integer divider;

    // constructors

    @Override
    protected boolean matchesSafely(Integer dividend) {
        if (divider == 0) {
            return false;
        }
        return ((dividend % divider) == 0);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("divisible by " + divider);
    }

    public static Matcher divisibleBy(Integer divider) {
        return new IsDivisibleBy(divider);
    }
}

Bastante simples,we just added a new attribute to our class and assigned it during construction. Em seguida, passamos como um parâmetro para o nosso método estático:

@Test
public void givenAnEvenInteger_whenDivisibleByTwo_thenCorrect() {
    Integer ten = 10;
    Integer two = 2;

    assertThat(ten,is(divisibleBy(two)));
}

@Test
public void givenAnOddInteger_whenNotDivisibleByTwo_thenCorrect() {
    Integer eleven = 11;
    Integer two = 2;

    assertThat(eleven,is(not(divisibleBy(two))));
}

E é isso! Já temos nosso matcher usando mais de uma entrada!

6. Conclusão

O Hamcrest fornece correspondentes que cobrem a maioria dos casos de uso com os quais um desenvolvedor geralmente precisa lidar ao criar asserções.

Além do mais, se algum caso específico não for coberto,Hamcrest also gives support to create custom matchers to be used under specific scenarios - como exploramos aqui. Eles são simples de criar e são usados ​​exatamente como os incluídos na biblioteca.

Para obter a implementação completa desses exemplos, consultethe GitHub Project.