Статьи

Насмешки, шпионы, частичные издевательства и окурки

Эта статья является частью нашего курса Академии под названием « Тестирование с помощью Mockito» .

В этом курсе вы погрузитесь в магию мокито. Вы узнаете о издевательствах, шпионах и частичных издевательствах, а также их соответствующем поведении. Вы также увидите процесс проверки с помощью тестовых двойников и сопоставителей объектов. Наконец, обсуждается разработка через тестирование (TDD) с Mockito, чтобы увидеть, как эта библиотека вписывается в концепцию TDD. Проверьте это здесь !

1. Введение

В этом уроке мы подробно рассмотрим классы и интерфейсы-заглушки, использующие Mockito.

2. Насмешка, заглушка, шпион — что за имя?

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

  • Mock (существительное) — объект, который действует как двойник для другого объекта.
  • Mock (глагол) — для создания фиктивного объекта или заглушки метода.
  • Шпион (существительное) — объект, который украшает существующий объект и позволяет добавлять методы этого объекта и проверять вызовы этого объекта.
  • Spy (глагол) — для создания и использования объекта Spy.
  • Заглушка (существительное) — объект, который может предоставлять «стандартные ответы» при вызове его методов.
  • Заглушка (глагол) — для создания постоянного ответа.
  • Partial Mock, Partial Stub (глагол) — еще один термин для шпиона, некоторые из его методов заглушены.

Технически, Mockito — это скорее Test Spy Framework, чем Mocking Framework, потому что он позволяет нам создавать шпионов и проверять поведение, а также создавать фиктивные объекты с тупым поведением.

Как мы видели в последнем уроке , мы можем использовать методы when().thenReturn() чтобы заглушить поведение данного интерфейса или класса. Теперь мы рассмотрим все способы, которыми мы можем предоставить заглушки для шуток и шпионов.

3. Сглаживание пустого метода

Учитывая следующий интерфейс:

1
2
3
4
5
public interface Printer {
 
    void printTestPage();
 
}

И следующий упрощенный класс ‘текстового процессора’ на основе строкового буфера, который его использует:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class StringProcessor {
 
    private Printer printer;
    private String currentBuffer;
 
    public StringProcessor(Printer printer) {
        this.printer = printer;
    }
 
    public Optional<String> statusAndTest() {
        printer.printTestPage();
        return Optional.ofNullable(currentBuffer);
    }
 
}

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

Вот наш тестовый класс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class StringProcessorTest {
 
    private Printer printer;
 
    @Test
    public void internal_buffer_should_be_absent_after_construction() {
        // Given
        StringProcessor processor = new StringProcessor(printer);
 
        // When
        Optional<String> actualBuffer = processor.statusAndTest();
 
        // Then
        assertFalse(actualBuffer.isPresent());
    }
}

Мы знаем, что statusAndTest() будет включать в себя вызов printTestPage() Printer и что ссылка на printer не инициализируется, поэтому мы получим NullPointerException если мы выполним этот тест. Чтобы избежать этого, нам просто нужно аннотировать тестовый класс, чтобы JUnit запустил его с Mockito, и аннотировать принтер как макет, чтобы сказать mockito создать макет для него.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@RunWith(MockitoJUnitRunner.class)
public class StringProcessorTest {
 
    @Mock
    private Printer printer;
 
    @Test
    public void internal_buffer_should_be_absent_after_construction() {
        // Given
        StringProcessor processor = new StringProcessor(printer);
 
        // When
        Optional<String> actualBuffer = processor.statusAndTest();
 
        // Then
        assertFalse(actualBuffer.isPresent());
    }
 
}

Теперь мы можем выполнить наш тест, и Mockito создаст для нас реализацию Printer и назначит ее экземпляр переменной принтера. Мы больше не будем получать исключение NullPointerException.

Но что, если Printer — это класс, который действительно выполняет какую-то работу, например, печатает физическую тестовую страницу? Что если бы мы выбрали @Spy вместо него, вместо создания @Mock ? Помните, что шпион будет вызывать реальные методы шпионящего класса, если только они не заглушены. Мы бы хотели избегать каких-либо реальных действий при вызове метода. Давайте сделаем простую реализацию Printer:

1
2
3
4
5
6
7
8
public class SysoutPrinter implements Printer {
 
    @Override
    public void printTestPage() {
        System.out.println("This is a test page");
    }
 
}

И добавьте его в качестве шпиона в наш тестовый класс и добавьте новый метод для тестирования с его помощью:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Spy
    private SysoutPrinter sysoutPrinter;
 
@Test
    public void internal_buffer_should_be_absent_after_construction_sysout() {
        // Given
        StringProcessor processor = new StringProcessor(sysoutPrinter);
 
        // When
        Optional<String> actualBuffer = processor.statusAndTest();
 
        // Then
        assertFalse(actualBuffer.isPresent());
    }

Если вы выполните этот тест сейчас, вы увидите следующий вывод на консоли:

1
This is a test page

Это подтверждает, что наш тестовый пример на самом деле выполняет реальный метод класса SysoutPrinter из-за того, что это шпион, а не мошенник. Если бы класс действительно выполнил реальный физический отпечаток тестовой страницы, это было бы крайне нежелательно!

Когда мы делаем частичную имитацию или Spy, мы можем org.mockito.Mockito.doNothing() метод, чтобы гарантировать, что в нем ничего не происходит, с помощью org.mockito.Mockito.doNothing() .

Давайте добавим следующий импорт и тест:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
import static org.mockito.Mockito.*;
 
@Test
    public void internal_buffer_should_be_absent_after_construction_sysout_with_donothing() {
        // Given
        StringProcessor processor = new StringProcessor(sysoutPrinter);
        doNothing().when(sysoutPrinter).printTestPage();
 
        // When
        Optional<String> actualBuffer = processor.statusAndTest();
 
        // Then
        assertFalse(actualBuffer.isPresent());
    }

Обратите внимание на цепочку методов doNothing.when(sysoutPrinter).printTestPage() : это говорит Mockito, что при вызове void метода printTestPage из @Spy sysoutPrinter что настоящий метод не должен выполняться, и вместо этого ничего не следует делать. Теперь, когда мы выполняем этот тест, мы не видим вывод на экран.

Что если мы расширим наш интерфейс Printer, чтобы создать новое исключение PrinterNotConnectedException если физический принтер не подключен. Как мы можем проверить этот сценарий?

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

1
2
3
4
5
public class PrinterNotConnectedException extends Exception {
 
    private static final long serialVersionUID = -6643301294924639178L;
 
}

И измените наш интерфейс, чтобы бросить его:

1
void printTestPage() throws PrinterNotConnectedException;

Нам также нужно изменить StringProcessor чтобы что-то делать с исключением, если оно было StringProcessor . Ради простоты мы просто выбросим исключение обратно в вызывающий класс.

1
public Optional<String> statusAndTest() throws PrinterNotConnectedException

Теперь мы хотим проверить, что исключение передается вызывающему классу, поэтому мы должны заставить Принтер выбросить его. Аналогично doNothing() мы можем использовать doThrow для принудительного исключения.

Давайте добавим следующий тест:

01
02
03
04
05
06
07
08
09
10
11
12
@Test(expected = PrinterNotConnectedException.class)
    public void printer_not_connected_exception_should_be_thrown_up_the_stack() throws Exception {
        // Given
        StringProcessor processor = new StringProcessor(printer);
        doThrow(new PrinterNotConnectedException()).when(printer).printTestPage();
 
        // When
        Optional<String> actualBuffer = processor.statusAndTest();
 
        // Then
        assertFalse(actualBuffer.isPresent());
    }

Здесь мы видим, что мы можем использовать doThrow() чтобы генерировать любые исключения, которые мы хотим. В этом случае мы PrinterNotConnectedException которое удовлетворит наш тест.

Теперь, когда мы узнали, как заглушить пустые методы, давайте посмотрим, как вернуть некоторые данные.

4. Заглушка возвращаемых значений

Давайте начнем создавать объект доступа к данным для сохранения и извлечения объектов клиентов из базы данных. Этот DAO будет использовать корпоративный Java- EntityManager под капотом для выполнения реальных взаимодействий с БД.

Чтобы использовать EntityManager мы будем использовать реализацию JPA 2.0 в Hibernate, добавив следующую зависимость в ваш файл pom.xml:

1
2
3
4
5
<dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.0-api</artifactId>
            <version>1.0.1.Final</version>
        </dependency>

Теперь мы создадим простую сущность Customer, которая будет представлять постоянных клиентов.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Entity
public class Customer {
 
    @Id @GeneratedValue
    private long id;
    private String name;
    private String address;
 
    public Customer() {
 
    }
 
    public Customer(long id, String name, String address) {
        super();
        this.id = id;
        this.name = name;
        this.address = address;
    }
 
    public long getId() {
        return id;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    public String getAddress() {
        return address;
    }
 
    public void setAddress(String address) {
        this.address = address;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}

Теперь мы создадим скелет DAO, который использует @PersistenceContext для настройки @PersistenceContext EntityManager . Нам не нужно беспокоиться об использовании Java Persistence Architecture (JPA) или о том, как она работает — мы будем использовать Mockito, чтобы полностью обойти ее, но это служит хорошим примером реального применения Mockito в действии.

01
02
03
04
05
06
07
08
09
10
public class CustomerDAO {
 
    @PersistenceContext
    EntityManager em;
 
    public CustomerDAO(EntityManager em) {
        this.em = em;
    }
 
}

Мы будем добавлять базовые функциональные возможности получения и обновления в нашу DAO и тестировать ее с помощью Mockito.

Начнем с метода Retrieve — мы передадим ID и вернем соответствующего Клиента из БД, если они существуют.

1
2
3
public Optional<Customer> findById(long id) throws Exception {
        return Optional.ofNullable(em.find(Customer.class, id));
    }

Здесь мы используем Java Optional чтобы избежать необходимости делать нулевые проверки результатов.

Теперь мы можем добавить тесты для тестирования этого метода, когда клиент найден, а клиент не найден — мы заглушим метод find() для возврата соответствующего Optional в каждом случае, используя методы org.mockito.Mockito.when и затем thenReturn()

Давайте создадим наш класс Test следующим образом ( import static org.mockito.Mockito.*; Для методов Mockito):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@RunWith(MockitoJUnitRunner.class)
public class CustomerDAOTest {
 
    private CustomerDAO dao;
 
    @Mock
    private EntityManager mockEntityManager;
 
    @Before
    public void setUp() throws Exception {
        dao = new CustomerDAO(mockEntityManager);
    }
 
    @Test
    public void finding_existing_customer_should_return_customer() throws Exception {
        // Given
        long expectedId = 10;
        String expectedName = "John Doe";
        String expectedAddress = "21 Main Street";
        Customer expectedCustomer = new Customer(expectedId, expectedName, expectedAddress);
 
        when(mockEntityManager.find(Customer.class, expectedId)).thenReturn(expectedCustomer);
 
        // When
        Optional<Customer> actualCustomer = dao.findById(expectedId);
 
        // Then
        assertTrue(actualCustomer.isPresent());
        assertEquals(expectedId, actualCustomer.get().getId());
        assertEquals(expectedName, actualCustomer.get().getName());
        assertEquals(expectedAddress, actualCustomer.get().getAddress());
    }
}

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

Первые строки включают создание Customer с известными ожидаемыми значениями, затем мы видим вызов Mockito, который сообщает ему, чтобы он возвращал этого клиента, когда вызывается метод EntityManager.find() с конкретными входными параметрами, которые мы ему передаем. Затем мы выполняем фактическое выполнение метода findById() и группы утверждений, чтобы гарантировать получение ожидаемых значений.

Давайте рассмотрим вызов Mockito:

1
when(mockEntityManager.find(Customer.class, expectedId)).thenReturn(expectedCustomer);

Это демонстрирует мощный, элегантный синтаксис Mockito. Это почти читается как обычный английский. Когда метод find() объекта mockEntityManager вызывается с конкретными входными данными Customer.class и expectedId mockEntityManager , тогда возвращается объект expectedCustomer объект.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Test
    public void invoking_mock_with_unexpected_argument_returns_null() throws Exception {
        // Given
        long expectedId = 10L;
        long unexpectedId = 20L;
        String expectedName = "John Doe";
        String expectedAddress = "21 Main Street";
        Customer expectedCustomer = new Customer(expectedId, expectedName, expectedAddress);
 
        when(mockEntityManager.find(Customer.class, expectedId)).thenReturn(expectedCustomer);
 
        // When
        Optional<Customer> actualCustomer = dao.findById(unexpectedId);
 
        // Then
        assertFalse(actualCustomer.isPresent());
    }

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
    public void invoking_mock_with_different_argument_returns_different_customers() throws Exception {
        // Given
        long expectedId1 = 10L;
        String expectedName1 = "John Doe";
        String expectedAddress1 = "21 Main Street";
        Customer expectedCustomer1 = new Customer(expectedId1, expectedName1, expectedAddress1);
 
        long expectedId2 = 20L;
        String expectedName2 = "Jane Deer";
        String expectedAddress2 = "46 High Street";
        Customer expectedCustomer2 = new Customer(expectedId2, expectedName2, expectedAddress2);
 
        when(mockEntityManager.find(Customer.class, expectedId1)).thenReturn(expectedCustomer1);
        when(mockEntityManager.find(Customer.class, expectedId2)).thenReturn(expectedCustomer2);
 
        // When
        Optional<Customer> actualCustomer1 = dao.findById(expectedId1);
        Optional<Customer> actualCustomer2 = dao.findById(expectedId2);
 
        // Then
        assertEquals(expectedName1, actualCustomer1.get().getName());
        assertEquals(expectedName2, actualCustomer2.get().getName());
    }

Вы можете даже соединить цепочку возвратов, чтобы заставить макет делать что-то другое в каждом вызове. Обратите внимание, что если вы вызываете макет больше раз, чем используете заглушку, он будет продолжать вести себя в соответствии с последней заглушкой навсегда.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
    public void invoking_mock_with_chained_stubs_returns_different_customers() throws Exception {
        // Given
        long expectedId1 = 10L;
        String expectedName1 = "John Doe";
        String expectedAddress1 = "21 Main Street";
        Customer expectedCustomer1 = new Customer(expectedId1, expectedName1, expectedAddress1);
 
        long expectedId2 = 20L;
        String expectedName2 = "Jane Deer";
        String expectedAddress2 = "46 High Street";
        Customer expectedCustomer2 = new Customer(expectedId2, expectedName2, expectedAddress2);
 
        when(mockEntityManager.find(Customer.class, expectedId1))
            .thenReturn(expectedCustomer1).thenReturn(expectedCustomer2);
 
        // When
        Optional<Customer> actualCustomer1 = dao.findById(expectedId1);
        Optional<Customer> actualCustomer2 = dao.findById(expectedId1);
 
        // Then
        assertEquals(expectedName1, actualCustomer1.get().getName());
        assertEquals(expectedName2, actualCustomer2.get().getName());
    }

Обратите внимание, что мы ввели один и тот же идентификатор в оба вызова, другое поведение поддерживается вторым theReturn() , это работает только потому, что часть заглушки when() явно ожидает и ввод в поле theReturn() , если мы передали expectedId2 мы получил бы нулевой ответ от макета из-за того, что это не ожидаемое значение в заглушке.

Теперь давайте проверим случай, когда клиент отсутствует.

01
02
03
04
05
06
07
08
09
10
11
12
@Test
    public void finding_missing_customer_should_return_null() throws Exception {
        // Given
        long expectedId = 10L;
        when(mockEntityManager.find(Customer.class, expectedId)).thenReturn(null);
 
        // When
        Optional<Customer> actualCustomer = dao.findById(expectedId);
 
        // Then
        assertFalse(actualCustomer.isPresent());
    }

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

Mockito позволяет вам использовать VarArgs в thenReturn для thenReturn последовательных вызовов, поэтому, если мы захотим, мы можем свернуть предыдущие два теста в один следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@Test
    public void finding_customer_should_respond_appropriately() throws Exception {
        // Given
        long expectedId = 10L;
        String expectedName = "John Doe";
        String expectedAddress = "21 Main Street";
        Customer expectedCustomer1 = new Customer(expectedId, expectedName, expectedAddress);
        Customer expectedCustomer2 = null;
 
        when(mockEntityManager.find(Customer.class, expectedId)).thenReturn(expectedCustomer1, expectedCustomer2);
 
        // When
        Optional<Customer> actualCustomer1 = dao.findById(expectedId);
        Optional<Customer> actualCustomer2 = dao.findById(expectedId);
 
        // Then
        assertTrue(actualCustomer1.isPresent());
        assertFalse(actualCustomer2.isPresent());
    }

Что если наш метод find вызывает исключение из-за некоторой проблемы с сохранением? Давайте проверим это!

01
02
03
04
05
06
07
08
09
10
11
12
13
@Test(expected=IllegalArgumentException.class)
    public void finding_customer_should_throw_exception_up_the_stack() throws Exception {
        // Given
        long expectedId = 10L;
 
        when(mockEntityManager.find(Customer.class, expectedId)).thenThrow(new IllegalArgumentException());
 
        // When
        dao.findById(expectedId);
 
        // Then
        fail("Exception should be thrown.");
    }

Мы использовали метод thenThrow() чтобы thenThrow() наше исключение. Сравните этот синтаксис с нашим использованием doThrow() когда doThrow() методы. Это два похожих, но разных метода — thenThrow() не будет работать с методами void.

4.1. Использование ответов

Выше мы видели, что создали клиента с определенными ожидаемыми значениями. Если мы хотим создать несколько известных тестовых пользователей и вернуть им базу с их идентификаторами, мы могли бы использовать Answer который мы могли бы вернуть из наших вызовов when() . Answer — это универсальный тип, предоставляемый Mockito для предоставления «стандартных ответов». Его метод answer() принимает объект InvocationOnMock который содержит определенную информацию о текущем вызове фиктивного метода.

Давайте создадим 3 клиентов и ответ, чтобы выбрать, кого из них вернуть, основываясь на идентификаторе ввода.

Сначала 3 клиента добавляются как частные члены тестового класса.

1
private Customer homerSimpson, bruceWayne, tyrionLannister;

Затем добавьте частный метод setupCustomers для их инициализации и вызовите его из метода @Before .

01
02
03
04
05
06
07
08
09
10
11
@Before
    public void setUp() throws Exception {
        dao = new CustomerDAO(mockEntityManager);
        setupCustomers();
    }
 
    private void setupCustomers() {
        homerSimpson = new Customer(1, "Homer Simpson", "Springfield");
        bruceWayne = new Customer(2, "Bruce Wayne", "Gotham City");
        tyrionLannister = new Customer(2, "Tyrion Lannister", "Kings Landing");
    }

И теперь мы можем создать Answer для возврата соответствующего Customer на основе идентификатора, который был передан методу find() переданному в mock EntityManager во время выполнения.

01
02
03
04
05
06
07
08
09
10
11
12
13
private Answer<Customer> withCustomerById = new Answer<Customer>() {
        @Override
        public Customer answer(InvocationOnMock invocation) throws Throwable {
            Object[] args = invocation.getArguments();
            int id = ((Long)args[1]).intValue(); // Cast to int for switch.
            switch (id) {
            case 1 : return homerSimpson;
            case 2 : return bruceWayne;
            case 3 : return tyrionLannister;
            default : return null;
            }
        }
    };

Мы можем видеть, что мы используем InvocationOnMock чтобы получить аргументы, которые были переданы в вызов метода Mock. Мы знаем, что вторым аргументом является идентификатор, поэтому мы можем прочитать его и определить подходящего клиента для возврата. Имя ответа withCustomerById будет соответствовать нашему withCustomerById синтаксису позже.

Теперь давайте напишем тест, который демонстрирует этот ответ в действии.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Test
    public void finding_customer_by_id_returns_appropriate_customer() throws Exception {
        // Given
        long[] expectedId = {1, 2, 3};
 
        when(mockEntityManager.find(eq(Customer.class), anyLong())).thenAnswer(withCustomerById);
 
        // When
        Optional<Customer> actualCustomer0 = dao.findById(expectedId[0]);
        Optional<Customer> actualCustomer1 = dao.findById(expectedId[1]);
        Optional<Customer> actualCustomer2 = dao.findById(expectedId[2]);
 
        // Then
        assertEquals("Homer Simpson", actualCustomer0.get().getName());
        assertEquals("Bruce Wayne", actualCustomer1.get().getName());
        assertEquals("Tyrion Lannister", actualCustomer2.get().getName());
    }

Давайте посмотрим на строку окурок в деталях.

1
when(mockEntityManager.find(eq(Customer.class), anyLong())).thenAnswer(withCustomerById);

Здесь мы видим пару новых вещей. Во-первых, вместо того, чтобы делать when().thenReturn() мы делаем when().thenAnswer() и предоставляем наш ответ withCustomerById в качестве ответа, который будет дан. Во-вторых, мы не используем действительное значение для идентификатора, переданного в mockEntityManager.find() вместо этого мы используем статический org.mockito.Matchers.anyLong() . Это Matcher и он используется для того, чтобы Mockito запустил Ответ, не проверяя, передано ли определенное значение Long. Matchers позволяют игнорировать параметры ложного вызова и вместо этого концентрируются только на возвращаемом значении.

Мы также украсили Customer.class с помощью eq() Matcher — это связано с тем, что вы не можете смешивать реальные значения и соответствия в вызовах метода Mock, вы должны либо иметь все параметры как Matchers, либо все параметры как реальные значения. eq() предоставляет Matcher, который соответствует только тогда, когда параметр времени выполнения равен указанному параметру в заглушке. Это позволяет нам возвращать ответ только в том случае, если тип входного класса имеет тип Customer.class без указания конкретного идентификатора.

Все это означает, что три вызова mockEntityManager.find() с разными идентификаторами приводят к получению одного и того же Ответа, и, поскольку мы закодировали Ответ, чтобы ответить соответствующими объектами Customer для разных идентификаторов, мы успешно смоделировали EntityManager способный имитировать реалистичное поведение.

4.2. Замечание по соглашениям о тестировании, управляемых поведением

Возможно, вы заметили, что в наших модульных тестах мы приняли соглашение о разбиении теста на 3 части — // Дано, // Когда и // Затем. Это соглашение называется Behavior Driven Development и является очень логичным способом разработки модульных тестов.

  • / / Данный этап установки, на котором мы инициализируем данные и заглушку классов. Это то же самое, что заявить «учитывая следующие начальные условия».
  • // Когда это фаза выполнения, где мы выполняем тестируемый метод и фиксируем все возвращаемые объекты.
  • // Затем наступает этап проверки, на котором мы размещаем логику утверждения, которая проверяет, ведет ли метод себя так, как ожидается.

Mockito поддерживает BDD из коробки в классе org.mockito.BDDMockito . Он заменяет обычные методы- thenReturn()when() , thenReturn() , thenThrow() , thenAnswer() т. Д. На thenAnswer() BDD — willReturn() , willThrow() , willAnswer() , willAnswer() . Это позволяет нам избегать использования when() в разделе // Given, поскольку это может сбивать с толку.

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

Позволяет переписать finding_existing_customer_should_return_customer() с использованием синтаксиса BDDMockito.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
import static org.mockito.BDDMockito.*;
 
@Test
    public void finding_existing_customer_should_return_customer_bdd() throws Exception {
        // Given
        long expectedId = 10L;
        String expectedName = "John Doe";
        String expectedAddress = "21 Main Street";
        Customer expectedCustomer = new Customer(expectedId, expectedName, expectedAddress);
 
        given(mockEntityManager.find(Customer.class, expectedId)).willReturn(expectedCustomer);
 
        // When
        Optional<Customer> actualCustomer = dao.findById(expectedId);
 
        // Then
        assertTrue(actualCustomer.isPresent());
        assertEquals(expectedId, actualCustomer.get().getId());
        assertEquals(expectedName, actualCustomer.get().getName());
        assertEquals(expectedAddress, actualCustomer.get().getAddress());
    }

Логика теста не изменилась, она просто более читабельна в форме BDD.

4,3. Совет по использованию статического метода Mockito в Eclipse

Если вы хотите избежать импорта org.mockito.Mockito.* т org.mockito.Mockito.* Д., Чтобы вручную добавить статический импорт для различных статических методов org.mockito.Mockito.* и перейдите к Java / Editor / Content Assist / Favorites в левой навигационной панели. После этого добавьте следующее как «Новый тип…» согласно рисунку 1.

  • org.mockito.Mockito
  • org.mockito.Matchers
  • org.mockito.BDDMockito

Это добавит статические методы Mockito в Eclipse Content Assist, что позволит вам автоматически заполнять и импортировать их по мере их использования.

Рисунок 1 - Избранное Помощника по контенту

Рисунок 1 — Избранное Помощника по контенту

4.4. Использование нескольких Mocks

Теперь мы рассмотрим использование нескольких макетов в комбинации. Давайте добавим метод в нашу DAO, чтобы получить список всех доступных клиентов.

1
2
3
4
public List<Customer> findAll() throws Exception {
        TypedQuery<Customer> query = em.createQuery("select * from CUSTOMER", Customer.class);
        return query.getResultList();
    }

Здесь мы видим, что метод createQuery() EntityManager возвращает типизированный типизированный TypedQuery . Он принимает в качестве параметров строку SQL и класс, который является типом возвращаемого значения. Сам TypedQuery предоставляет несколько методов, включая List getResultList() который можно использовать для выполнения запросов, которые возвращают несколько значений, таких как наш select * from CUSTOMER запроса select * from CUSTOMER выше.

Чтобы написать тест для этого метода, мы хотим создать Mock of TypedQuery .

1
2
@Mock
private TypedQuery<Customer> mockQuery;

Теперь мы можем заглушить этот фиктивный запрос, чтобы получить список известных клиентов. Давайте создадим ответ, чтобы сделать это, и повторно использовать известных клиентов, которых мы создали ранее. Возможно, вы заметили, что Ответ — это функциональный интерфейс, имеющий только один метод. Мы используем Java 8, поэтому мы можем создать лямбда-выражение для представления нашего встроенного ответа, а не анонимного внутреннего класса, как мы делали в предыдущем примере ответа.

1
given(mockQuery.getResultList()).willAnswer(i -> Arrays.asList(homerSimpson, bruceWayne, tyrionLannister));

Конечно, мы могли бы также кодировать заглушку как

1
given(mockQuery.getResultList()).willReturn(Arrays.asList(homerSimpson, bruceWayne, tyrionLannister));given

что демонстрирует гибкость Mockito — всегда есть несколько разных способов сделать одно и то же.

Теперь мы остановили поведение ложного TypedQuery мы можем TypedQuery ложное EntityManager чтобы оно возвращалось по запросу. Вместо того, чтобы anyString() SQL в наш тестовый пример, мы просто будем использовать Matcher anyString() чтобы запустить mock createQuery() , конечно же, мы также createQuery() параметр класса createQuery() eq() .

Полный тест выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
@Test
    public void finding_all_customers_should_return_all_customers() throws Exception {
        // Given
        given(mockQuery.getResultList()).willAnswer(i -> Arrays.asList(homerSimpson, bruceWayne, tyrionLannister));
        given(mockEntityManager.createQuery(anyString(), eq(Customer.class))).willReturn(mockQuery);
 
        // When
        List<Customer> actualCustomers = dao.findAll();
 
        // Then
        assertEquals(actualCustomers.size(), 3);
    }

4,5. Проверь себя! Тестовое обновление!

Давайте добавим метод Update() DAO:

1
2
3
public Customer update(Customer customer) throws Exception {
    return em.merge(customer);
}

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

5. Аргументы соответствия

Естественное поведение Mocktio заключается в использовании метода equals() объекта, который передается в качестве параметра, чтобы увидеть, применимо ли определенное поведение с заглушкой. Однако можно избежать использования реальных объектов и переменных при создании заглушки, если нам неважно, что это за значения. Мы делаем это с помощью Mockito Argument Matchers

Мы уже видели несколько работающих сопоставителей аргументов Mockito: anyLong() , anyString() и eq . Мы используем эти средства сопоставления, когда нам не особенно важны входные данные для Mock, нас интересует только кодирование поведения возврата, и мы хотим, чтобы он вел себя одинаково при любых условиях.

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

Все org.mockito.ArgumentMatcher сопоставления аргументов расширяют org.mockito.ArgumentMatcher а Mockito включает в себя библиотеку готовых сопоставителей аргументов, доступ к которым можно получить через статические методы org.mockito.Matchers , чтобы использовать их, просто импортировав org.mockito.Matchers.* ;

Вы можете посмотреть на javadoc для org.mockito.Matchers чтобы увидеть все Matchers, которые предоставляет Mockito, в то время как следующий тестовый класс демонстрирует использование некоторых из них:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package com.javacodegeeks.hughwphamill.mockito.stubbing;
 
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
 
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
 
@RunWith(MockitoJUnitRunner.class)
public class MatchersTest {
 
    public interface TestForMock {
 
        public boolean usesPrimitives(int i, float f, double d, byte b, boolean bool);
 
        public boolean usesObjects(String s, Object o, Integer i);
 
        public boolean usesCollections(List<String> list, Map<Integer, String> map, Set<Object> set);
 
        public boolean usesString(String s);
 
        public boolean usesVarargs(String... s);
 
        public boolean usesObject(Object o);
 
    }
 
    @Mock
    TestForMock test;
 
    @Test
    public void test() {
 
        // default behaviour is to return false
        assertFalse(test.usesString("Hello"));
 
        when(test.usesObjects(any(), any(), any())).thenReturn(true);
        assertTrue(test.usesObjects("Hello", new Thread(), 17));
        Mockito.reset(test);
 
        when(test.usesObjects(anyString(), anyObject(), anyInt())).thenReturn(true);
        assertTrue(test.usesObjects("Hi there", new Float(18), 42));
        Mockito.reset(test);
 
        when(test.usesPrimitives(anyInt(), anyFloat(), anyDouble(), anyByte(), anyBoolean())).thenReturn(true);
        assertTrue(test.usesPrimitives(1, 43.4f, 3.141592654d, (byte)2, false));
        Mockito.reset(test);
 
        // Gives unchecked type conversion warning
        when(test.usesCollections(anyList(), anyMap(), anySet())).thenReturn(true);
        assertTrue(test.usesCollections(Arrays.asList("Hello", "World"), Collections.EMPTY_MAP, Collections.EMPTY_SET));
        Mockito.reset(test);
 
        // Gives no warning
        when(test.usesCollections(anyListOf(String.class), anyMapOf(Integer.class, String.class), anySetOf(Object.class))).thenReturn(true);
        assertTrue(test.usesCollections(Collections.emptyList(), Collections.emptyMap(), Collections.emptySet()));
        Mockito.reset(test);
 
        // eq() must match exactly
        when(test.usesObjects(eq("Hello World"), any(Object.class),anyInt())).thenReturn(true);
        assertFalse(test.usesObjects("Hi World", new Object(), 360));
        assertTrue(test.usesObjects("Hello World", new Object(), 360));
        Mockito.reset(test);
 
        when(test.usesString(startsWith("Hello"))).thenReturn(true);
        assertTrue(test.usesString("Hello there"));
        Mockito.reset(test);
 
        when(test.usesString(endsWith("something"))).thenReturn(true);
        assertTrue(test.usesString("isn't that something"));
        Mockito.reset(test);
 
        when(test.usesString(contains("second"))).thenReturn(true);
        assertTrue(test.usesString("first, second, third."));
        Mockito.reset(test);
 
        // Regular Expression
        when(test.usesString(matches("^\\\\w+$"))).thenReturn(true);
        assertTrue(test.usesString("Weak_Password1"));
        assertFalse(test.usesString("@Str0nG!pa$$woR>%42"));
        Mockito.reset(test);
 
        when(test.usesString((String)isNull())).thenReturn(true);
        assertTrue(test.usesString(null));
        Mockito.reset(test);
 
        when(test.usesString((String)isNotNull())).thenReturn(true);
        assertTrue(test.usesString("Anything"));
        Mockito.reset(test);
 
        // Object Reference
        String string1 = new String("hello");
        String string2 = new String("hello");
        when(test.usesString(same(string1))).thenReturn(true);
        assertTrue(test.usesString(string1));
        assertFalse(test.usesString(string2));
        Mockito.reset(test);
 
        // Compare to eq()
        when(test.usesString(eq(string1))).thenReturn(true);
        assertTrue(test.usesString(string1));
        assertTrue(test.usesString(string2));
        Mockito.reset(test);
 
        when(test.usesVarargs(anyVararg())).thenReturn(true);
        assertTrue(test.usesVarargs("A","B","C","D","E"));
        assertTrue(test.usesVarargs("ABC", "123"));
        assertTrue(test.usesVarargs("Hello!"));
        Mockito.reset(test);
 
        when(test.usesObject(isA(String.class))).thenReturn(true);
        assertTrue(test.usesObject("A String Object"));
        assertFalse(test.usesObject(new Integer(7)));
        Mockito.reset(test);
 
        // Field equality using reflection
        when(test.usesObject(refEq(new SomeBeanWithoutEquals("abc", 123)))).thenReturn(true);
        assertTrue(test.usesObject(new SomeBeanWithoutEquals("abc", 123)));
        Mockito.reset(test);
 
        // Compare to eq()
        when(test.usesObject(eq(new SomeBeanWithoutEquals("abc", 123)))).thenReturn(true);
        assertFalse(test.usesObject(new SomeBeanWithoutEquals("abc", 123)));
        Mockito.reset(test);
 
        when(test.usesObject(eq(new SomeBeanWithEquals("abc", 123)))).thenReturn(true);
        assertTrue(test.usesObject(new SomeBeanWithEquals("abc", 123)));
        Mockito.reset(test);
    }
 
    public class SomeBeanWithoutEquals {
        private String string;
        private int number;
 
        public SomeBeanWithoutEquals(String string, int number) {
            this.string = string;
            this.number = number;
        }
    }
 
    public class SomeBeanWithEquals {
        private String string;
        private int number;
 
        public SomeBeanWithEquals(String string, int number) {
            this.string = string;
            this.number = number;
        }
 
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + getOuterType().hashCode();
            result = prime * result + number;
            result = prime * result
                    + ((string == null) ? 0 : string.hashCode());
            return result;
        }
 
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            SomeBeanWithEquals other = (SomeBeanWithEquals) obj;
            if (!getOuterType().equals(other.getOuterType()))
                return false;
            if (number != other.number)
                return false;
            if (string == null) {
                if (other.string != null)
                    return false;
            } else if (!string.equals(other.string))
                return false;
            return true;
        }
 
        private MatchersTest getOuterType() {
            return MatchersTest.this;
        }
    }
}

Также возможно создавать свои собственные Matchers, расширяя org.mockito.ArgumentMatcher . Давайте создадим совпадение, которое срабатывает, если List содержит определенный элемент. Мы также создадим статический удобный метод для создания Matcher, который использует argThat для преобразования Matcher в List для использования в вызове stubbing. Мы будем реализовывать метод matches() чтобы вызывать метод содержимого объекта List для выполнения нашей фактической проверки содержимого.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class ListContainsMatcher<T> extends ArgumentMatcher<List<T>> {
 
    private T element;
 
    public ListContainsMatcher(T element) {
        this.element = element;
    }
 
    @Override
    public boolean matches(Object argument) {
        @SuppressWarnings("unchecked")
        List<T> list = (List<T>) argument;
        return list.contains(element);
    }
 
    public static <T> List<T> contains(T element) {
        return argThat(new ListContainsMatcher<>(element));
    }
}

А теперь тест, чтобы продемонстрировать наш новый Matcher в действии!

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RunWith(MockitoJUnitRunner.class)
public class ListContainsMatcherTest {
 
    public interface TestClass {
        public boolean usesStrings(List<String> list);
        public boolean usesIntegers(List<Integer> list);
    }
 
    private List<String> stringList = Arrays.asList("Hello", "Java", "Code", "Geek");
    private List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
 
    @Mock
    TestClass test;
 
    @Test
    public void test() throws Exception {
        when(test.usesStrings(contains("Java"))).thenReturn(true);
        when(test.usesIntegers(contains(5))).thenReturn(true);
        assertTrue(test.usesIntegers(integerList));
        assertTrue(test.usesStrings(stringList));
        Mockito.reset(test);
 
        when(test.usesStrings(contains("Something Else"))).thenReturn(true);
        when(test.usesIntegers(contains(42))).thenReturn(true);
        assertFalse(test.usesStrings(stringList));
        assertFalse(test.usesIntegers(integerList));
        Mockito.reset(test);
    }
}

В качестве упражнения попробуйте написать свой собственный Matcher, который будет соответствовать, если карта содержит определенную пару ключ / значение.

6. Шпионы и частичная заглушка

Как мы видели ранее, можно частично @Spy класс с @Spy аннотации @Spy . Частичная заглушка позволяет нам использовать реальный класс в наших тестах и ​​заглушать только то поведение, которое нас интересует.Рекомендации Mockito говорят нам, что шпионов следует использовать осторожно и время от времени, обычно при работе с устаревшим кодом. Лучшей практикой является не использование Spy для частичной насмешки над тестируемым классом, а частичная имитация зависимостей. Тестируемый класс всегда должен быть реальным объектом.

Давайте представим, что мы имеем дело с классом обработки изображений, который работает с java.awt.BufferedImage. Этот класс возьмет в свой BufferedImageконструктор и предоставит метод, чтобы заполнить изображение случайными цветными вертикальными полосами и вернуть миниатюру изображения, основываясь на входной высоте миниатюры.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class ImageProcessor {
 
    private BufferedImage image;
 
    public ImageProcessor(BufferedImage image) {
        this.image = image;
    }
 
    public Image overwriteImageWithStripesAndReturnThumbnail(int thumbHeight) {
        debugOutputColorSpace();
 
        Random random = new Random();
        Color color = new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));
 
        for (int x = 0; x < image.getWidth(); x++) {
            if (x % 20 == 0) {
                color = new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));
                for (int y = 0; y < image.getHeight(); y++) {
                    image.setRGB(x, y, color.getRGB());
                }
            }
        }
 
        Image thumbnail = image.getScaledInstance(-1, thumbHeight, Image.SCALE_FAST);
 
        Image microScale = image.getScaledInstance(-1, 5, Image.SCALE_DEFAULT);
        debugOutput(microScale);
        return thumbnail;
    }
 
    private void debugOutput(Image microScale) {
        System.out.println("Runtime type of microScale Image is " + microScale.getClass());
 
    }
 
    private void debugOutputColorSpace() {
        for (int i=0; i< image.getColorModel().getColorSpace().getNumComponents(); i++) {
            String componentName = image.getColorModel().getColorSpace().getName(i);
            System.out.println(String.format("Colorspace Component[%d]: %s", i, componentName));
        }
    }
}

В overwriteImageWithStripesAndReturnThumbnail()методе много чего происходит . Первое, что он делает — выводит отладочную информацию о цветовом пространстве изображения. Затем он генерирует случайные цвета и рисует их в виде горизонтальных полос по всему изображению, используя методы ширины и высоты изображений. Затем выполняется операция масштабирования, чтобы вернуть изображение для представления миниатюры. Затем он выполняет операцию второго масштаба для создания небольшого диагностического микроизображения и выводит тип класса выполнения этого микроизображения в качестве отладочной информации.

Мы видим много взаимодействий с BufferedImage, большинство из которых полностью внутренние или случайные. В конечном итоге, когда мы хотим проверить поведение нашего метода, для нас важен первый вызов getScaledInstance()— наш класс работает, если возвращаемое значение нашего метода — это объект, который возвращается из getScaledInstance (). Это поведение BufferedImage, которое нам важно заглушить. Проблема, с которой мы сталкиваемся, состоит в том, что существует множество других вызовов методов BufferedImages. Мы не особо заботимся о возвращаемых значениях этих методов с точки зрения тестирования, но если мы не закодируем поведение для них, они как-то вызовут NullPointerExceptions и, возможно, другое нежелательное поведение.

Чтобы обойти эту проблему, мы создадим Spy для BufferedImage и только заглушим getScaledInstance()интересующий нас метод.

Давайте создадим пустой тестовый класс с тестируемым классом и созданным Spy, а также макет для возвращенного эскиза.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@RunWith(MockitoJUnitRunner.class)
public class ImageProcessorTest {
 
    private ImageProcessor processor;
 
    @Spy
    private BufferedImage imageSpy = new BufferedImage(800, 600, BufferedImage.TYPE_INT_ARGB);
    @Mock
    Image mockThumbnail;
 
    @Before
    public void setup() {
        processor = new ImageProcessor(imageSpy);
    }
}

Обратите внимание, что BufferedImage не имеет конструктора по умолчанию, поэтому нам пришлось создавать его сами, используя его параметризованный конструктор, если бы у него был конструктор по умолчанию, мы могли бы позволить Mockito создать его экземпляр для нас.

Теперь давайте сделаем первую попытку заглушить интересующее нас поведение. Имеет смысл игнорировать входную высоту, ширину и режим, а также использовать аргументы соответствия аргументов для всех трех. В итоге получается что-то вроде следующего:

1
given(imageSpy.getScaledInstance(anyInt(), anyInt(), anyInt())).willReturn(mockThumbnail);

Обычно это лучший способ заглушки для шпиона, однако в этом случае есть проблема — imageSpy является реальным BufferedImage, а передаваемый вызов заглушки given()является реальным вызовом метода, который фактически выполняется, когда операция заглушки выполняется JVM. getScaledInstanceтребует, чтобы ширина и высота были ненулевыми, поэтому этот вызов приведет к IllegalArgumentExceptionброску.

Одним из возможных решений является использование реальных аргументов в нашем вызове заглушки

01
02
03
04
05
06
07
08
09
10
11
@Test
    public void scale_should_return_internal_image_scaled() throws Exception {
        // Given
        given(imageSpy.getScaledInstance(-1, 100, Image.SCALE_FAST)).willReturn(mockThumbnail);
 
        // When
        Image actualImage = processor.overwriteImageWithStripesAndReturnThumbnail(100);
 
        // Then
        assertEquals(actualImage, mockThumbnail);
    }

Этот тест выполняется успешно и выдает следующий вывод на консоль

1
2
3
4
Colorspace Component[0]: Red
Colorspace Component[1]: Green
Colorspace Component[2]: Blue
Runtime type of microScale Image is class sun.awt.image.ToolkitImage

Побочный эффект от использования реальных значений заключается в том, что второй вызов для getScaledInstance()создания микро-образа для отладки не совпадает, и в этот момент выполняется реальный метод в BufferedImage, а не наше поведение с заглушкой — вот почему мы видим реальный тип времени выполнения выводится микроизображение, а не реализация mockito mock, которую мы увидим, если mockThumbnail передан методу вывода отладки.

Но что, если мы захотим продолжить использование Argument Matchers? Можно использовать doReturn()метод (обычно используемый для void-методов, если вы помните), чтобы заглушить getScaledInstance()метод без его фактического вызова во время заглушки.

01
02
03
04
05
06
07
08
09
10
11
@Test
public void scale_should_return_internal_image_scaled_doReturn() throws Exception {
    // Given
    doReturn(mockThumbnail).when(imageSpy).getScaledInstance(anyInt(), anyInt(), anyInt());
 
    // When
    Image actualImage = processor.overwriteImageWithStripesAndReturnThumbnail(100);
 
    // Then
    assertEquals(actualImage, mockThumbnail);
}

Это дает следующий вывод:

1
2
3
4
Colorspace Component[0]: Red
Colorspace Component[1]: Green
Colorspace Component[2]: Blue
Runtime type of microScale Image is class $java.awt.Image$$EnhancerByMockitoWithCGLIB$$72355119

Вы можете видеть, что тип времени выполнения микроизображения теперь является реализацией Mock, созданной Mockito. Это так, потому что оба вызова getScaledInstanceсовпадают с аргументами заглушки и, таким образом, миниатюра макета возвращается из обоих вызовов.

Существует способ гарантировать, что реальный метод Spy вызывается во втором случае, это с помощью doCallRealMethod()метода Mockito. Как обычно, Mockito позволяет вам связывать воедино методы-заглушки, чтобы кодировать различное поведение для последовательных вызовов метода-заглушки, которые соответствуют аргументам-заглушкам.

01
02
03
04
05
06
07
08
09
10
11
@Test
    public void scale_should_return_internal_image_scaled_doReturn_doCallRealMethod() throws Exception {
        // Given
        doReturn(mockThumbnail).doCallRealMethod().when(imageSpy).getScaledInstance(anyInt(), anyInt(), anyInt());
 
        // When
        Image actualImage = processor.overwriteImageWithStripesAndReturnThumbnail(100);
 
        // Then
        assertEquals(actualImage, mockThumbnail);
    }

Что дает следующий вывод

1
2
3
4
Colorspace Component[0]: Red
Colorspace Component[1]: Green
Colorspace Component[2]: Blue
Runtime type of microScale Image is class sun.awt.image.ToolkitImage

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

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

Javadoc для Mockito является хорошим источником информации о методах Stubbing и особенно о ArgumentMatchers, которые Mockito предоставляет «из коробки».

Мы подробно рассмотрели поведение заглушки, и в следующем уроке мы рассмотрим проверку поведения Mocks с использованием инфраструктуры проверки Mockito.

8. Загрузите исходный код

Это был урок о Мокито Стаббинге. Вы можете скачать исходный код здесь: mockito2-stubbing