«1. Введение

Этой статьей мы начнем новую серию статей, посвященных набору инструментов для имитации JMockit.

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

В последующих статьях основное внимание будет уделено его возможностям.

2. JMockit

2.1. Введение

Прежде всего, давайте поговорим о том, что такое JMockit: Java-фреймворк для имитации объектов в тестах (вы можете использовать его как для JUnit, так и для TestNG).

Он использует инструментальные API-интерфейсы Java для изменения байт-кода классов во время выполнения, чтобы динамически изменять их поведение. Некоторыми из его сильных сторон являются его выразимость и нестандартная способность имитировать статические и частные методы.

Может быть, вы новичок в JMockit, но это точно не из-за того, что он новый. Разработка JMockit началась в июне 2006 года, а его первый стабильный выпуск датируется декабрем 2012 года, так что он существует уже некоторое время (на момент написания статьи текущая версия — 1.24).

2.2. Зависимость Maven

Во-первых, нам нужно добавить в наш проект зависимость jmockit:

<dependency> 
    <groupId>org.jmockit</groupId> 
    <artifactId>jmockit</artifactId> 
    <version>1.41</version>
</dependency>

2.3. Выразимость JMockit

Как было сказано ранее, одной из самых сильных сторон JMockit является его выразимость. Чтобы создавать моки и определять их поведение, вместо вызова методов из API-мокинга вам просто нужно определить их напрямую.

Это означает, что вы не будете делать такие вещи, как:

API.expect(mockInstance.method()).andThenReturn(value).times(2);

Вместо этого ожидайте таких вещей, как:

new Expectation() {
    mockInstance.method(); 
    result = value; 
    times = 2;
}

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

Если принять во внимание, что в части result = value вы можете вернуть что угодно (фиксированные значения, динамически генерируемые значения, исключения и т. д.), выразительность JMockit становится еще более очевидной.

2.4. Модель Record-Replay-Verify

Тесты с использованием JMockit делятся на три дифференцированных этапа: запись, воспроизведение и проверка.

  1. On the record phase, during test preparation and before the invocations to the methods we want to be executed, we will define the expected behavior for all tests to be used during the next stage.
  2. The replay phase is the one in which the code under test is executed. The invocations of mocked methods/constructors previously recorded on the previous stage will now be replayed.
  3. Lastly, on the verify phase, we will assert that the result of the test was the one we expected (and that mocks behaved and were used according to what was defined in the record phase).

На примере кода каркас для теста будет выглядеть примерно так:

@Test
public void testWireframe() {
   // preparation code not specific to JMockit, if any

   new Expectations() {{ 
       // define expected behaviour for mocks
   }};

   // execute code-under-test

   new Verifications() {{ 
       // verify mocks
   }};

   // assertions
}

3. Создание макетов

3.1. Аннотации JMockit

При использовании JMockit самый простой способ использовать макеты — это использовать аннотации. Есть три для создания макетов (@Mocked, @Injectable и @Capturing) и один для указания тестируемого класса (@Tested).

При использовании аннотации @Mocked для поля будут создаваться фиктивные экземпляры каждого нового объекта этого конкретного класса.

С другой стороны, с аннотацией @Injectable будет создан только один фиктивный экземпляр.

Последняя аннотация, @Capturing, будет вести себя как @Mocked, но будет распространяться на каждый подкласс, расширяющий или реализующий тип аннотированного поля.

3.2. Передача аргументов в тесты

При использовании JMockit можно передавать макеты в качестве параметров теста. Это очень полезно для создания макета только для этого одного теста, например, для какого-то сложного объекта модели, которому требуется определенное поведение, например, только для одного теста. Это будет что-то вроде этого:

@RunWith(JMockit.class)
public class TestPassingArguments {
   
   @Injectable
   private Foo mockForEveryTest;

   @Tested
   private Bar bar;

   @Test
   public void testExample(@Mocked Xyz mockForJustThisTest) {
       new Expectations() {{
           mockForEveryTest.someMethod("foo");
           mockForJustThisTest.someOtherMethod();
       }};

       bar.codeUnderTest();
   }
}

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

3.3. Полный пример

В конце этой статьи мы добавим полный пример теста с использованием JMockit.

В этом примере мы будем тестировать класс Performer, который использует Collaborator в своем методе Perform(). Этот метод Perform() получает объект Model в качестве параметра, из которого он будет использовать свой метод getInfo(), который возвращает строку, эта строка будет передана в метод Collaborator() из Collaborator, который вернет true для этого конкретного теста, и это значение будет передано методу receive() из Collaborator.

Итак, тестируемые классы будут выглядеть так:

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

public class Collaborator {
    public boolean collaborate(String string){
        return false;
    }
    public void receive(boolean bool){
        // NOOP
    }
}

public class Performer {
    private Collaborator collaborator;
	
    public void perform(Model model) {
        boolean value = collaborator.collaborate(model.getInfo());
        collaborator.receive(value);
    }
}

А код теста будет таким:

@RunWith(JMockit.class)
public class PerformerTest {

    @Injectable
    private Collaborator collaborator;

    @Tested
    private Performer performer;

    @Test
    public void testThePerformMethod(@Mocked Model model) {
        new Expectations() {{
    	    model.getInfo();result = "bar";
    	    collaborator.collaborate("bar"); result = true;
        }};
        performer.perform(model);
        new Verifications() {{
    	    collaborator.receive(true);
        }};
    }
}

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

«На этом мы завершим наше практическое знакомство с JMockit. Если вы хотите узнать больше о JMockit, следите за будущими статьями.

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

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

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

    JMockit 101 Руководство по JMockit — ожидания Расширенное использование JMockit