JavaRush /Курсы /JSP & Servlets /Мокирование объектов

Мокирование объектов

JSP & Servlets
4 уровень , 2 лекция
Открыта

3.1 Метод doReturn()

А теперь будет магия…

Допустим, ты создал фейковый мок-объект, но ведь нужно чтобы он как-то работал. При вызове определенных методов делалось что-то важное или методы возвращали определенный результат. Что делать?

Библиотека Mockito позволяет добавить мок-объекту нужное поведение.

Если ты хочешь, чтобы при вызове определенного метода, мок-объект вернул определенный результат, то это “правило” можно добавить объекту с помощью кода:

Mockito.doReturn(результат).when(объект).имяМетода();

Видишь, в конце вызова метода имяМетода? На самом деле никакого вызова тут не происходит. Метод doReturn() возвращает специальный proxy-объект с помощью которого следит за вызовами методов объекта и, таким образом, идет запись правила.

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

Думаю, нужен конкретный пример. Давайте создадим мок-объект класса ArrayList и попросим его метод size() вернуть число 10. Полный вариант кода будет выглядеть так:


@ExtendWith(MockitoExtension.class)
class DoReturnTest {
    @Mock
    List mockList;
 
    @Test
    public void whenMockAnnotation () {
         //создаем правило: вернуть 10 при вызове метода size
        Mockito.doReturn(10).when(mockList).size();

        //тут вызывается метод и вернет 10!!
        assertEquals(10, mockList.size());  
    }
}

Да, этот код будет работать, тест не упадет.

3.2 Метод when()

Есть еще один способ добавить правило поведения к мок-объекту – через вызов метода Mockito.when(). Выглядит вот так:

Mockito.when(объект.имяМетода()).thenReturn(результат);

Это такой же способ записи правила поведения мок-объекта, как и предыдущий. Сравните:

Mockito.doReturn(результат).when(объект).имяМетода();

Тут происходит абсолютно одно и то же – конструирование нового правила.

Правда первый пример имеет два минуса:

  • вызов объект.имяМетода() сильно сбивает с толку.
  • не будет работать, если метод имяМетода() возвращает void.

Ну и давайте запишем полюбившийся нам пример с помощью Mockito.when()


@ExtendWith(MockitoExtension.class)
class WhenTest {
    @Mock
    List mockList;
 
    @Test
    public void whenMockAnnotation() {
        //создаем правило: вернуть 10 при вызове метода size
        Mockito.when(mockList.size() ).thenReturn(10); 

        //тут вызывается метод и вернет 10!!
        assertEquals(10, mockList.size());  
    }
}

3.3 Метод doThrow()

Мы разобрались, как сделать так, чтобы метод мок-объекта вернул определенный результат. А как сделать так, чтобы он кинул определенное исключение? Передать его в doReturn()?

Чтобы метод не вернул, а именно выбросил (throw) исключение, нужно задать правило с помощью метода doThrow().

Mockito.doThrow(исключение.class).when(объект).имяМетода();

И сразу второй вариант:

Mockito.when(объект.имяМетода()).thenThrow(исключение.class);

Немного ожидаемо, да?

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


@ExtendWith(MockitoExtension.class)
class DoThrowTest {
    @Mock
    List mockList;
 
    @Test
    public void whenMockAnnotation() {
        Mockito.when(mockList.size() ).thenThrow(IllegalStateException.class);
        mockList.size(); //тут кинется исключение
    }
}

Если нужно выкинуть определенный объект-исключение, то воспользуйся конструкцией вида:

Mockito.doThrow(new Исключение()).when(объект).имяМетода();

Просто передай в метод doThrow() объект исключения и он будет выброшен во время вызова метода.

Комментарии (10)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Олег Уровень 106 Expert
30 августа 2024
Пока ничего не понял
Рубен Уровень 62
21 сентября 2024
я вообще не понял что это за хрень
Anonymous #885613 Уровень 40
12 марта 2023


public class User {
    public Exception throwException() {
        throw new IllegalArgumentException();
    }
}


...
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class MockTest {

    @Mock
    User user;

    @Test
    public void whenThrowExceptionIAE() {
        when(user.throwException()).thenThrow(new IllegalArgumentException());
        assertThrows(IllegalArgumentException.class,() -> {
            user.throwException();
        });
    }
}
Redya Daniel Уровень 16
25 февраля 2023
Понятно, но зачем это?
Justinian Уровень 41 Master
28 августа 2023
Это необходимо для юнит тестов. Представим структуру: Класс1 { класс2 //поле класса класс3 //поле класса Класс2 класс4 //поле класса Класс3 класс5 //поле класса Класс4 ... и так далее В больших и даже средних программах, классы имеют поля - мы используем сервис, а в нем внутри есть взаимодействие с репозиторием, и валидатором к примеру и с внешним сервисом и еще с чем-то. Основной вид тестов для программиста - это компонентный тест. Мы НЕ проверяем работу всей программы, в реальных джава приложениях это ооочень сложная структура может быть и тест такой написать еще тот вызов. Такие тесты (интеграционные ) долго и сложно писать, и они долго исполняются. Поэтому в среднем, 80-95% тестов которые пишут программисты это компонентные, остальные уже немного интеграционных. А вот QA инженеры вручную или автоматизировано тестируют поведение всей системы. Теперь рассмотрим структуру классов выше: Каждый класс использует другой, другой третий и тд. У нас же метод звучит так:

public List<String> getListByAccountId(int accountId) {
  if (accountId < 1) {
    throw SomeServiceException(format("Account id=%d is invalid", accountId));
  }

  int generalId = class1.doSomething();
  int expectedId = class2.calculateExpectedId();
  int desiredId = expectedId + generalId - accountId;

  return  repository.getList(desiredId);;
}
мы пишем компонентные тесты, они аналогичны лампочкам или датчикам в авто: датчик давления шин, датчик уровня масла, датчик дождя, датчик темпреатуры двигателя, датчик исправности 1, и тд. То есть это маленькие, конкретные датчики по КОНКРЕТНОМУ КОМПОНЕНТУ (компонент = юнит, оттого и юнит/компонентный тест) и в этом и есть смысл. Что отдельно есть приемка всего автомобиля , это делают тестировщики, они тестируют автомобиль весь по сути, а программист, как разработчик конкретного компонента
Justinian Уровень 41 Master
28 августа 2023
(мы ведь на работе делаем конкретную таску, написать конкретный функционал = конкретный прибор, и к нему автоматом пишем юнит тест - проверочный датчик. И прошел тест - зеленый свет, не прошел тест - красный), этот датчик информативен и четко и конкретно указывает на проблему, если загорелся датчик "перегрев двигателя" мы сразу понимаем что нужно делать и где проблема. Поэтому юнит тесты небольшие но в них есть смысл. Но они проверяют конкретный компонент. Они не проверяют взаимосвязь со всем, это не их задача, поэтому в приведенном мной примере тест будет примерно выглядет так:

@Test
void shouldSuccessfullyReturnSomeListByAccountId() {
  List<String> expected = List.of("some1", "some2");

  when(class1.doSomething).thenReturn(10);
  when(class2.calculateExpectedId()).thenReturn(20);
  when(repository.getList(17)).thenReturn(expected);

  List<String> actual = sut.getListByAccountId(13);

  assertIterableEquals(expected, actual);
 }
+ тест на исключение + если есть ифы или развлетвления логики - мы тестируем и их Что мы видим, что мы написали юнит тест, мы конкретно проверяем логику конкретного метода. А не всей программы сразу как там работает тот класс, как работает другое, как репозиторий..у них свои тесты есть. Нас интересует работа нашего кода, и если мы изменим что-то в методе сервиса, который тестируем - добавим +1 или изменим логику, то тесты начнут фейлится, что от них и требуется. А что там возвращают класс1, класс2 или репозиторий нам все равно, мы тестируем код нашего метода, ифы, исключения и логику конкретного метода. А не всю программу целиком со всеми классами. На реальных энтерпрайз проектах это почти невозможно или очень сложно, поскольку нужно поднимать контекст, идти в веб, конфигурировать, а там миллион вопросов по паролям, окружениям, серверам и тд. Задача же компонентных тестов, быстро пишутся, быстро исполняются, быстро показывают что не так.
18 января 2023
А что, если надо проверить как наш код обрабатывает выбрасываемое исключение замоканным void методом?
Екатерина Уровень 70 Expert
19 ноября 2022
Я примерно с многопоточки перестала понимать что происходит))) 🤣
14 октября 2022
Толку от этого? если мы задаем сами, то что должен метод возвращать (не смотря на внутреннюю бизнес логику метода). Мы можем просто сделать when(Scanner.nextLine()).thenReturn(15); Хоть nextLine не возвращает Int, мы пройдем тест прописав assertEquals( 15 , Scanner.nextLine() ); Это то же самое что сказать, А=15 и спросить Равен ли а==15?
Stas S Уровень 108 Expert
5 сентября 2022
Так в чем разница между "исключение.class" и "new Исключение()" , переданных в doThrow/thenThrow? Метод не будет вызван, отработает правило, заданное для него.