Руководство по ожиданиям JMockit

1. Вступление

Эта статья является второй статьей в серии JMockit. Вы можете прочитать ссылку:/jmockit-101[первая статья], поскольку мы предполагаем, что вы уже знакомы с основами JMockit.

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

2. Соответствие значений аргумента

Следующие подходы применимы как к Expectations , так и к Verification .

2.1. «Любые» поля

JMockit предлагает набор служебных полей для более общего сопоставления аргументов. Одной из этих утилит являются поля anyX .

Они будут проверять, что любое значение было передано, и есть одно для каждого примитивного типа (и соответствующего класса-оболочки), одно для строк и «универсальное» типа Object .

Давайте посмотрим на пример:

public interface ExpectationsCollaborator {
    String methodForAny1(String s, int i, Boolean b);
    void methodForAny2(Long l, List<String> lst);
}

@Test
public void test(@Mocked ExpectationsCollaborator mock) throws Exception {
    new Expectations() {{
        mock.methodForAny1(anyString, anyInt, anyBoolean);
        result = "any";
    }};

    Assert.assertEquals("any", mock.methodForAny1("barfooxyz", 0, Boolean.FALSE));
    mock.methodForAny2(2L, new ArrayList<>());

    new FullVerifications() {{
        mock.methodForAny2(anyLong, (List<String>) any);
    }};
}

Вы должны принять во внимание, что при использовании поля any вам необходимо привести его к ожидаемому типу. Полный список полей представлен в documentation .

2.2. Методы «с»

JMockit также предоставляет несколько методов, которые помогут с общим сопоставлением аргументов. Это методы withX .

Они позволяют немного более сложное сопоставление, чем поля anyX .

Здесь мы видим пример, в котором мы определим ожидание для метода, который будет вызван строкой, содержащей foo , целое число, не равное 1, ненулевое Boolean и любой экземпляр класса List :

public interface ExpectationsCollaborator {
    String methodForWith1(String s, int i);
    void methodForWith2(Boolean b, List<String> l);
}

@Test
public void testForWith(@Mocked ExpectationsCollaborator mock) throws Exception {
    new Expectations() {{
        mock.methodForWith1(withSubstring("foo"), withNotEqual(1));
        result = "with";
    }};

    assertEquals("with", mock.methodForWith1("barfooxyz", 2));
    mock.methodForWith2(Boolean.TRUE, new ArrayList<>());

    new Verifications() {{
        mock.methodForWith2(withNotNull(), withInstanceOf(List.class));
    }};
}

Вы можете увидеть полный список withX методов в documentation JMockit .

Учтите, что специальные with (Delegate) и withArgThat (Matcher) будут рассмотрены в их собственном подразделе.

2.3. Ноль не нуль

То, что лучше понять раньше, чем позже, - это то, что null не используется для определения аргумента, для которого null был передан в макет.

На самом деле, null используется как синтаксический сахар для определения того, что любой объект будет передан (поэтому он может использоваться только для параметров ссылочного типа). Чтобы конкретно проверить, что данный параметр получает ссылку null , можно использовать withNull () matcher.

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

public interface ExpectationsCollaborator {
    String methodForNulls1(String s, List<String> l);
    void methodForNulls2(String s, List<String> l);
}

@Test
public void testWithNulls(@Mocked ExpectationsCollaborator mock){
    new Expectations() {{
        mock.methodForNulls1(anyString, null);
        result = "null";
    }};

    assertEquals("null", mock.methodForNulls1("blablabla", new ArrayList<String>()));
    mock.methodForNulls2("blablabla", null);

    new Verifications() {{
        mock.methodForNulls2(anyString, (List<String>) withNull());
    }};
}

Обратите внимание на разницу: null означает любой список, а withNull () означает null ссылку на список. В частности, это исключает необходимость приведения значения к объявленному типу параметра (смотрите, что третий аргумент должен быть приведен, но не второй).

Единственное условие, позволяющее использовать это, заключается в том, что для ожидания использовался хотя бы один явный сопоставитель аргументов (либо метод with , либо поле any ).

2.4. Поле «Таймс»

Иногда мы хотим ограничить количество вызовов ** ожидаемых для смоделированного метода. Для этого в JMockit есть зарезервированные слова times , minTimes и maxTimes (все три допускают только неотрицательные целые числа).

public interface ExpectationsCollaborator {
    void methodForTimes1();
    void methodForTimes2();
    void methodForTimes3();
}

@Test
public void testWithTimes(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{
        mock.methodForTimes1(); times = 2;
        mock.methodForTimes2();
    }};

    mock.methodForTimes1();
    mock.methodForTimes1();
    mock.methodForTimes2();
    mock.methodForTimes3();
    mock.methodForTimes3();
    mock.methodForTimes3();

    new Verifications() {{
        mock.methodForTimes3(); minTimes = 1; maxTimes = 3;
    }};
}

В этом примере мы определили, что ровно два вызова (не один, не три, а ровно два) methodForTimes1 () должны выполняться с использованием строки times = 2;

Затем мы использовали поведение по умолчанию (если ограничение повторения не задано minTimes = 1; используется), чтобы определить, что по крайней мере один вызов будет выполнен для methodForTimes2 () .

Наконец, используя minTimes = 1; с последующим maxTimes = 3; мы определили, что для methodForTimes3 () будет происходить от одного до трех вызовов.

Имейте в виду, что и minTimes , и maxTimes могут быть указаны для одного и того же ожидания, если minTimes назначается первым. С другой стороны, times может использоваться только один.

2.5. Настраиваемый аргумент

Иногда сопоставление аргументов не так просто, как простое указание значения или использование некоторых предопределенных утилит ( anyX или withX ).

В этом случае JMockit использует интерфейс Matcher Hamcrest ‘. Вам просто нужно определить сопоставление для конкретного сценария тестирования и использовать это сопоставление с помощью вызова withArgThat () .

Давайте посмотрим пример для сопоставления определенного класса с переданным объектом:

public interface ExpectationsCollaborator {
    void methodForArgThat(Object o);
}

public class Model {
    public String getInfo(){
        return "info";
    }
}

@Test
public void testCustomArgumentMatching(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{
        mock.methodForArgThat(withArgThat(new BaseMatcher<Object>() {
            @Override
            public boolean matches(Object item) {
                return item instanceof Model && "info".equals(((Model) item).getInfo());
            }

            @Override
            public void describeTo(Description description) { }
        }));
    }};
    mock.methodForArgThat(new Model());
}

3. Возвращаемые значения

Давайте теперь посмотрим на возвращаемые значения; имейте в виду, что следующие подходы применимы только к Expectations , поскольку никакие возвращаемые значения не могут быть определены для Verification .

3.1. Результат и возврат (…​)

При использовании JMockit у вас есть три различных способа определения ожидаемого результата вызова смоделированного метода. Из всех трех мы поговорим о первых двух (самых простых), которые наверняка охватят 90% случаев повседневного использования.

Это два поля result и метод returns (Object …​) :

  • С помощью поля result вы можете определить одно возвращаемое значение для

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

  • ** Несколько result полей назначения могут быть сделаны для возврата

  • более одного значения ** для более чем одного вызова метода (вы можете смешивать как возвращаемые значения, так и ошибки, которые будут выброшены).

  • ** То же самое поведение будет достигнуто при назначении result списка

или массив значений (того же типа, что и тип возвращаемого значения для смоделированного метода, без исключений здесь).

  • Метод returns (Object …​) является синтаксическим сахаром для возврата

несколько значений одного и того же времени.

Это легче показать с помощью фрагмента кода:

public interface ExpectationsCollaborator{
    String methodReturnsString();
    int methodReturnsInt();
}

@Test
public void testResultAndReturns(@Mocked Foo mock){
    new StrictExpectations() {{
        mock.methodReturnsString();
        result = "foo";
        result = new Exception();
        result = "bar";
        mock.methodReturnsInt(); result = new int[]{ 1, 2, 3 };
        mock.methodReturnsString(); returns("foo", "bar");
        mock.methodReturnsInt(); result = 1;
    }};

    assertEquals("Should return foo", "foo", mock.methodReturnsString());
    try {
        mock.methodReturnsString();
    } catch (Exception e) { }

    assertEquals("Should return bar", "bar", mock.methodReturnsString());
    assertEquals("Should return 1", 1, mock.methodReturnsInt());
    assertEquals("Should return 2", 2, mock.methodReturnsInt());
    assertEquals("Should return 3", 3, mock.methodReturnsInt());
    assertEquals("Should return foo", "foo", mock.methodReturnsString());
    assertEquals("Should return bar", "bar", mock.methodReturnsString());
    assertEquals("Should return 1", 1, mock.methodReturnsInt());
}

В этом примере мы определили, что для первых трех вызовов methodReturnsString () ожидаемые результаты (по порядку) «foo» , исключение и «bar» . Мы достигли этого, используя три разных назначения в поле result .

Затем в строке 14 мы определили, что для четвертого и пятого вызовов «foo» и «bar» должны возвращаться с использованием метода returns (Object …​) .

Для methodReturnsInt () мы определили в строке 13 , чтобы она возвращала 1, 2 и, наконец, 3, присваивая массив с различными результатами полю result , а в строке 15 мы определили, что возвращать 1, просто назначив result поле.

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

3.2. Delegators

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

Мы собираемся увидеть пример с простым объяснением:

public interface ExpectationsCollaborator {
    Object methodForDelegate(int i);
}

@Test
public void testDelegate(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{

        mock.methodForDelegate(anyInt);
        result = new Delegate() {
            public int delegate(int i) throws Exception {
                if(i < 3) {
                    return 5;
                } else {
                    throw new Exception();
                }
            }
        };
    }};

    assertEquals("Should return 5", 5, mock.methodForDelegate(1));
    try {
        mock.methodForDelegate(3);
    } catch (Exception e) { }
}

Способ использования делегатора - создать для него новый экземпляр и назначить его полю returns . В этом новом экземпляре вы должны создать новый метод с теми же параметрами и типом возвращаемого значения, что и у смоделированного метода (вы можете использовать любое имя для него). Внутри этого нового метода используйте любую реализацию, которую хотите, чтобы вернуть желаемое значение.

В этом примере мы реализовали реализацию, в которой 5 должно возвращаться, когда значение, передаваемое в mocked-метод меньше, чем 3 , и в противном случае выдается исключение (обратите внимание, что нам пришлось использовать times = 2; , чтобы второй вызов был ожидается, поскольку мы потеряли поведение по умолчанию, определив возвращаемое значение).

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

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

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

Мы, конечно, опубликуем больше статей о JMockit, так что следите за обновлениями, чтобы узнать больше.

И, как всегда, полную реализацию этого руководства можно найти на the проект GitHub

4.1. Статьи в серии

Все статьи серии:

  • ссылка:/jmockit-101[JMockit 101]

  • ссылка:/jmockit-ожидания[Руководство по ожиданиям JMockit]