«1. Введение

Помимо встроенных сопоставителей, Hamcrest также поддерживает создание пользовательских сопоставителей.

В этом уроке мы подробно рассмотрим, как их создавать и использовать. Чтобы получить краткий обзор доступных сопоставителей, обратитесь к этой статье.

2. Настройка пользовательских сопоставлений

Чтобы получить Hamcrest, нам нужно добавить следующую зависимость Maven в наш pom.xml:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>java-hamcrest</artifactId>
    <version>2.0.0.0</version>
    <scope>test</scope>
</dependency>

Последнюю версию Hamcrest можно найти на Maven Central.

3. Знакомство с TypeSafeMatcher

Прежде чем приступить к нашим примерам, важно понять класс TypeSafeMatcher. Нам придется расширить этот класс, чтобы создать собственный сопоставитель.

TypeSafeMatcher — абстрактный класс, поэтому все его подклассы должны реализовывать следующие методы:

    matchSafely(T t): содержит нашу логику сопоставления. не выполнено

Как видно из первого метода, TypeSafeMatcher параметризован, поэтому при его использовании нам придется объявлять тип. Это будет тип объекта, который мы тестируем.

Давайте проясним это, рассмотрев наш первый пример в следующем разделе.

4. Создание сопоставителя onlyDigits

Для нашего первого варианта использования мы создадим сопоставитель, который возвращает true, если определенная строка содержит только цифры.

Таким образом, onlyDigits, применяемые к «123», должны возвращать true, а «hello1» и «bye» должны возвращать false.

Начнем!

4.1. Создание сопоставления

Чтобы начать с нашего сопоставления, мы создадим класс, который расширяет TypeSafeMatcher:

public class IsOnlyDigits extends TypeSafeMatcher<String> {
   
    @Override
    protected boolean matchesSafely(String s) {
        // ...
    }

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

Обратите внимание, что поскольку объект, который мы будем тестировать, является текстом, мы параметризуем наш подкласс TypeSafeMatcher с помощью класс Строка.

Теперь мы готовы добавить нашу реализацию:

public class IsOnlyDigits extends TypeSafeMatcher<String> {

    @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");
    }
}

Как мы видим, matchSafey пытается преобразовать нашу входную строку в целое число. В случае успеха возвращается true. В случае неудачи возвращается false. Он успешно отвечает нашему варианту использования.

С другой стороны, descriptionTo прикрепляет текст, который представляет наши ожидания. Мы увидим, как это будет выглядеть дальше, когда мы будем использовать наш сопоставитель.

Нам нужно еще одно, чтобы завершить наш сопоставитель: статический метод для доступа к нему, чтобы он вел себя как остальные встроенные сопоставители.

Итак, мы добавим что-то вроде этого:

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

И все готово! Давайте посмотрим, как использовать этот сопоставитель в следующем разделе.

4.2. Использование Matcher

Чтобы использовать наш новый matcher, мы создадим тест:

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

    assertThat(digits, onlyDigits());
}

Вот и все. Этот тест пройдет, потому что входная строка содержит только цифры. Помните, что для того, чтобы сделать его более разборчивым, мы можем использовать сопоставитель is, который действует как обертка над любым другим сопоставителем:

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

Наконец, если мы запустим тот же тест, но с входными данными «123ABC», выходное сообщение будет таким:

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

Здесь мы видим текст, который мы добавили к методу descriptionTo. Как мы могли заметить, важно создать правильное описание того, что ожидается в тесте.

5. divisibleBy

Итак, что если мы хотим создать сопоставитель, определяющий, делится ли число на другое число? Для этого сценария нам нужно где-то сохранить один из параметров.

Давайте посмотрим, как мы можем это сделать:

public class IsDivisibleBy extends TypeSafeMatcher<Integer> {

    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<Integer> divisibleBy(Integer divider) {
        return new IsDivisibleBy(divider);
    }
}

Достаточно просто, мы просто добавили новый атрибут в наш класс и присвоили его во время построения. Затем мы просто передали его как параметр нашему статическому методу:

@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))));
}

И все! У нас уже есть наш сопоставитель, использующий более одного входа!

6. Заключение

Hamcrest предоставляет сопоставители, которые охватывают большинство случаев использования, с которыми обычно приходится иметь дело разработчику при создании утверждений.

Более того, если какой-либо конкретный случай не охвачен, Hamcrest также предоставляет поддержку для создания пользовательских сопоставителей, которые будут использоваться в определенных сценариях, как мы рассмотрели здесь. Их просто создать, и они используются точно так же, как те, что включены в библиотеку.

Чтобы получить полную реализацию этих примеров, обратитесь к проекту GitHub.