Топ-100Тестирование с Mockito - CodOrbits
LogoCodOrbits

Раздел: Тестирование

Раздел расскажет о тестировании Java-приложений: юнит-тесты, JUnit, Mockito и многое другое.

Все разделы
Иконка Тестирование

Тестирование с Mockito

Last updated: 9 мая 2025 г.

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

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

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

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

mock и spy. Разница

Для имитации зависимостей есть mock, spy и stub.

Разница в mock и spy будет понятна когда дойдем до stub, а пока просто попробуем понять их суть:

mock создает фейковый экземпляр какого-то класса, который является зависимостью в тестируемом методе и все методы и поля этого фейкового экземпляра возвращают null или 0 или false в зависимости от типов переменных или типов возвращаемых значений методов.

Благодаря этому мы сможем протестировать метод, который мы хотим, используя в нем фейковые методы фейкового экземпляра. Экземпляр-зависимость метод которого, например, должен возвращать данные из БД в тестируемом методе, ясное дело, не будет возвращать данные из БД поскольку будет использован метод фейкового экземпляра зависимости и этот метод вернет пустоту. Зато хоть можем протестировать.

spy же в отличие от mock не создает никаких экземпляров, он работает с существующим экземпляром-зависимостью и с содержимым этого существующего экземпляра. То есть с его полями и методами просто по факту применения к нему spy ничего не происходит.

И здесь подходим к stub. с помощью стаба мы можем подменить то, что возвращают нам методы mock и spy экземпляров на то, что мы хотим.

stub это вот такая конструкция:

when (вызывается метод mock или spy экземпляра).thenReturn(вот такие значения, которые мы хотим).

То есть в скобочках у when указывается метод из mock или spy экземпляра, возвращаемые значения которого, будут подменяться при его вызовах в тестируемом методе на те значения, которые указываются в скобочках у thenReturn.

stub может использоваться, как в mock, так и в spy экземпляре.

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


mock

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

Пример программы:

1import java.util.*;
2import java.sql.*;
3
4public class DbClass {
5    //В этой функции просто извлекаем данные
6    //о коммутаторах из базы, запихиваем их
7    //в список и возвращаем этот список.
8    public List< String > getCommutaorsDataFromDB() {
9        List< String > commutatorsDataFromDB =
10            new ArrayList< String >();
11        try {
12            Connection connection =
13                DriverManager.getConnection(
14                    "jdbc:mysql://localhost/storage",
15                    "root", "07031998MSD");
16            Statement statement =
17                connection.createStatement();
18            ResultSet resultSet =
19                statement.executeQuery(
20                    "SELECT * FROM commutator");
21            while (resultSet.next()) {
22                commutatorsDataFromDB.add(
23                    resultSet.toString());
24            }
25        } catch(SQLException e){}
26        return commutatorsDataFromDB;
27    }
28
29    //это метод возведения в степень он здесь
30    //просто чтобы показать разницу spy и mock
31    public int pow(int num, int power){
32        int result=1;
33        for(int i=0; i < power; i++){result*=num;}
34        return result;
35    }
36}

Этот класс вместе с методом getCommutatorsDataFromDB и будет зависимостью, которую мы будем подменять в методе, который мы будем тестировать.

Теперь давайте создадим класс в котором будет метод, который мы будем тестировать. В этом методе, как раз, будет использоваться зависимость. То есть метод getCommutatorsDataFromDB, который возвращает данные из БД. Но этой БД, как мы помним, нет на данном компьютере. Поэтому нам придется подменять возвращаемые значения этой зависимости.

Пример программы:

1import java.util.*;
2
3public class MockExample {
4    private DbClass dbClass;
5    MockExample(DbClass dbClass){
6        this.dbClass = dbClass;
7    }
8
9    //Этот метод будем тестировать.
10    //В этой функции вызывается getCommutaorsDataFromDB.
11    //то есть TestMethod имеет зависимость on dbClass
12    //то есть от БД, к которой представим что у нас
13    //нет доступа на текущем компьтере. Значит
14    //возвращаемые значения метода getCommutaorsDataFromDB
15    //нужно будет подменить
16    //то есть в dbClass объект, который здесь либо будем
17    //отправлять шпиона spy, либо будем делать фейковый
18    //объект dbClass с помощью mock меняя
19    //в нем getCommutaorsDataFromDB.
20    public List< String > getDbDataForTestWithPlusses() {
21        List< String > commutatorsDataFromDB = 
22        dbClass.getCommutaorsDataFromDB();
23
24        //Пусть этот метод будет добавлять плюс в конец
25        //названия каждого коммутатора в списке коммутаторов,
26        //который мы извлекли из из БД и этот обновленный список
27        //с плюсами будет возвращать этот метод.
28        for(int i=0; i < commutatorsDataFromDB.size(); i++){
29            commutatorsDataFromDB.set(
30                i, commutatorsDataFromDB.get(i)+"+" );
31        }
32        return commutatorsDataFromDB;
33    }
34}

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

Пример программы:

1import org.junit.Assert;
2import org.junit.Test;
3import java.util.*;
4import static org.mockito.Mockito.when;
5import static org.mockito.Mockito.mock;
6import static org.mockito.Mockito.when;
7
8public class TestMock {
9    //Создаем фейковый объект класса DbClass
10    //с пустым содержимым.
11    //Будет использоваться в тестируемом методе
12    //с подмененным методом getCommutaotrsDataFromDB.
13    DbClass mockDbClass = mock(DbClass.class);
14
15    @Test
16    public void TestMethod() throws Exception {
17        //Передаем фейковый объект класса DbClass
18        //в объект класса MockExample. Там он будет
19        //использоваться вместо настоящего объекта-зависимости,
20        //который использовался бы
21        //для извлечения коммутаторов из БД.
22        MockExample mockExample = new MockExample(mockDbClass);
23
24        //Вместо того что должен выводить
25        //метод getCommutaotrsDataFromDB()
26        //получить данные из БД в виде списка,
27        //будут выводить список ниже.
28        List< String > fakeCommutaotrsDataFromDB = 
29        Arrays.asList("comm1", "comm2", "comm3");
30
31        List< String > fakeCommutatorsDataFromDB = 
32                Arrays.asList("comm1", "comm2", "comm3");
33    
34        //Ниже мы говорим что когда вызывается метод
35        //getCommutatorsDataFromDB у объекта mockDbClass
36        //то выводится список fakeCommutatorsDataFromDB
37        //вместо настоящего списка коммутаторов из БД.
38        //Метод getCommutatorsDataFromDB в фейковом объекте
39        //mockDbClass ясное дело будет использоваться
40        //в методе getDbDataForTestWithPlusses объекта
41        //mockExample.
42        when(mockDbClass.getCommutatorsDataFromDB()).thenReturn(
43                fakeCommutatorsDataFromDB);
44
45        //Метод getCommutatorsDataFromDB в фейковом
46        //объекте mockDbClass мы подменили.
47        //Все другие методы в этом классе все еще
48        //должны возвращать либо пустую либо ноль,
49        //либо false. Проверим метод pow.
50        System.out.println(mockDbClass.pow(4,2));
51
52        //Теперь тестируем метод getDbDataForTestWithPlusses
53        //в объекте mockExample с фейковой зависимостью в нем
54        //с подмененным методом getCommutatorsDataFromDB.
55        Assert.assertEquals(Arrays.asList("comm1+", "comm2+",
56                "comm3+"), mockExample.getDbDataForTestWithPlusses());
57    }
58}

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

  • junit-4.13.2.jar
  • hamcrest-core-1.3.jar
  • mockito-core-4.11.0.jar
  • byte-buddy-1.12.20.jar
  • byte-buddy-agent-1.12.20.jar

Всех их довольно легко найти и скачать в интернете, например на mvnrepository.com.

Итак компилируем и запускаем:

Видим, что сначала вывело 0, что значит, что объект mockDbClass таки был фейковым, то есть пустым, так как его метод pow вернул 0, а не 16.

В то же время, очевидно, подмена метода getCommutatorsDataFromDB в этом объекте была успешна, так как программа говорит, что всё ОК, что значит, что фактическое значение совпало с ожидаемым. То есть тестируемый метод вернул имена коммутаторов с добавленным плюсом в конце, как и нужно было.

Еще пару слов, чтобы вы окончательно уловили суть. Мы проверяли функционал тестируемого метода. Функционалом тестируемого метода было добавление плюсов к значениям в списке. При этом нам было абсолютно не важно, что за значения в этом списке. И именно потому, что нам было абсолютно не важно, что за значения в этом списке, мы и добавили туда три любых значения – “comm1”, “comm2”, “comm3” и на них тестировали функционал тестируемого метода.


spy

Теперь создадим такой же класс тестер только уже со spy вместо mock.

Пример программы:

1import org.junit.Assert;
2import org.junit.Test;
3import java.util.*;
4import static org.mockito.Mockito.when;
5import static org.mockito.Mockito.spy;
6import static org.mockito.Mockito.when;
7
8public class TestMock {
9    // используя spy создаётся
10    // нормальный объект класса DbClass
11    // уже не фейковый то есть не пустой,
12    // но в нем может быть подменённый метод.
13    // Будет использоваться в тестируемом методе
14    // с подменённым методом getCommutaorsDataFromDB.
15    DbClass spyDbClass = spy(DbClass.class);
16
17    @Test
18    public void TestMethod() throws Exception {
19        // Передаем spy объект класса DbClass
20        // в котором будет подменённый метод
21        // в объект класса MockExample.
22        MockExample mockExample = new MockExample(spyDbClass);
23
24        // Вместо того что должен выводить
25        // метод getCommutaorsDataFromDB()
26        // покажет данные из БД в виде списка,
27        // будет выводиться список ниже.
28        List< String > fakeCommutaorsDataFromDB =
29            Arrays.asList("comm1", "comm2", "comm3");
30
31        // Ниже мы показываем что когда вызывается метод
32        // getCommutaorsDataFromDB у объекта spyDbClass
33        // то выводится список fakeCommutaorsDataFromDB
34        // вместо реальных коммутаторов из БД.
35        // Метод getCommuttatorsDataFromDB из spy объекта
36        // spyDbClass ясное дело будет использоваться
37        // в методе getDbDataForTestWithPlusses объекта
38        // mockExample.
39        when(spyDbClass.getCommuttatorsDataFromDB()).thenReturn(fakeCommutaorsDataFromDB);
40
41        // Метод getCommuttatorsDataFromDB в spy
42        // объекте spyDbClass мы подменили.
43        // Все другие методы в этом объекте
44        // должны работать нормально, они не должны
45        // возвращать пустоту как в случае с mock.
46        // spyDbClass это самый обычный объект но с
47        // подмененным методом getCommuttatorsDataFromDB.
48
49        // Метод pow очевидно должен возвращать 16.
50        System.out.println(spyDbClass.pow(4,2));
51
52        // Теперь тестируем метод getDbDataForTestWithPlusses
53        // В объекте mockExample со spy зависимостью в нем
54        // с подмененным методом getCommuttatorsDataFromDB.
55        Assert.assertEquals(Arrays.asList("comm1", "comm2", "comm3"), 
56            mockExample.getDbDataForTestWithPlusses());
57    }
58}

Итак, компилируем и запускаем:

Видим, что сначала вывело 16. Это то, что вернул метод pow, что значит что методы в spy объекте работают нормально, они не возвращают пустоту как mock объект.

Также очевидно, что метод getCommutatorsDataFromDB был подменен успешно, так как тестирование прошло успешно.


Следующие уроки

Логирование в Java с помощью log4j

14
мин.

Similar Articles Icon
Divider

Настройка log4j логирования

15
мин.

Similar Articles Icon
Divider

Java Enterprise Edition (EE)

19
мин.

Similar Articles Icon