Mockito е рамка с отворен код за създаване на mock-обекти в модулни тестове на Java и Kotlin, която позволява изолиране на тествания код от външни зависимости. С негова помощ разработчикът замества реални хранилища, API клиенти и бази данни с контролирани заглушки със зададено поведение. Според данни на Mockito.org, библиотеката се използва в повече от 60% от Java проектите, прилагащи модулно тестване.
Основни точки
Mockito е библиотека с отворен код за създаване на mock-обекти (заглушки) в модулни тестове на Java, Kotlin и други JVM езици. За разлика от JUnit, който отговаря за стартирането на тестове, Mockito решава проблема с изолирането — замества реалните зависимости на тествания клас с предвидими обекти.
Без мокове, тестването на метод, който има достъп до база данни или външно API, изисква настройка на реална среда — разгръщане на база данни, стартиране на сървър. Mockito замества тези зависимости с обекти с фиксирано поведение: методът repository.findById(1) винаги връща конкретен User обект, без да осъществява достъп до базата данни.
Архитектурата на Mockito се основава на модела Proxy (за интерфейси и класове). Библиотеката генерира подклас или прокси за указания тип и прихваща всички извиквания на методи, връщайки стойности по подразбиране или стойности, зададени чрез when().thenReturn().
Принципът на работа на Mockito се основава на три основни операции: създаване на мок, настройка на поведение (stubbing) и проверка на извиквания (verification). Всяка операция използва статични методи от класа org.mockito.Mockito — най-натоварения клас в екосистемата на Java според статистиката на Maven Central. Всички извиквания на методи на мок се записват в паметта, което позволява по-късна проверка чрез verify.
Типичният тест с Mockito се състои от три фази: Arrange — създаване на мокове и настройка на стабове чрез when().thenReturn(), Act — извикване на тествания метод, Assert — проверка на резултата чрез assertEquals и verify(mock). Този подход се нарича AAA (Arrange-Act-Assert).
Нека разгледаме прост тест, в който Mockito замества хранилището за потребители. Методът when().thenReturn() настройва мока така, че извикването findById да върне предварително подготвен User обект.
// Създаваме мок на хранилището
UserRepository mockRepo = mock(UserRepository.class);
// Настройваме поведението: при findById(1) върни потребителя
when(mockRepo.findById(1)).thenReturn(new User("Alice"));
// Проверяваме дали методът наистина е бил извикан
User result = mockRepo.findById(1);
assertEquals("Alice", result.getName());
verify(mockRepo).findById(1);
Mockito предоставя два начина за създаване на мокове: статичния метод mock(Class) и анотацията @Mock с инициализация чрез MockitoAnnotations.openMocks(). Първият начин е компактен за един-два мока, вторият е удобен, когато има много зависимости — анотациите намаляват boilerplate кода.
Методът mock() приема клас и връща обект-заглушка, който може да бъде настроен чрез when().thenReturn(). Всички ненастроени методи връщат стойности по подразбиране: 0 за числа, false за boolean, null за обекти.
ApiClient apiClient = mock(ApiClient.class);
Database database = mock(Database.class);
Анотацията @Mock в комбинация с @ExtendWith(MockitoExtension.class) автоматично създава мокове за всички полета на тестовия клас. Разширението MockitoExtension отговаря за инициализацията преди всеки тест.
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void getUserShouldReturnUserFromRepo() {
when(userRepository.findById(1)).thenReturn(new User("Alice"));
User result = userService.getUser(1);
assertEquals("Alice", result.getName());
}
}
Stubbing — процесът на определяне какво трябва да върне методът на мок при извикване с определени аргументи. Основен синтаксис: when(mock.method(args)).thenReturn(value). За различни сценарии Mockito предлага няколко варианта на then-методи.
| Метод | Предназначение |
|---|---|
| thenReturn(value) | Винаги връща указаната стойност |
| thenThrow(exception) | Хвърля изключение при извикване |
| thenAnswer(answer) | Изчислява връщаната стойност динамично |
| thenCallRealMethod() | Извиква реалния метод (частичен мок) |
Когато връщаната стойност зависи от аргументите на извикването, се използва thenAnswer с ламбда. Това е полезно за симулиране на работа с реални данни — например генериране на ID въз основа на предадения обект.
when(repository.save(any())).thenAnswer(invocation -> {
User user = invocation.getArgument(0);
user.setId(42);
return user;
});
Verify е уникална възможност на Mockito, която по-старите библиотеки за mock-обекти (EasyMock, jMock) не предоставят.
Verify прави тестовете по-надеждни, тъй като проверява не само връщаната стойност, но и страничните ефекти — извиквания на методи, които не връщат резултат (void методи). Методът verify(mock).methodName(args) проверява дали определен метод на мок е бил извикан с посочените аргументи. Това позволява тестване не само на резултата, но и на процеса — факта на обръщение към зависимостта.
По подразбиране verify проверява, че методът е бил извикан точно веднъж. Ако е необходим друг брой — се използват times(n), atLeast(n), never() и други модификатори от класа Mockito.
// Проверка на броя извиквания
verify(repository, times(3)).save(any());
verify(repository, never()).delete(any());
verify(repository, atLeastOnce()).findById(1);
// Проверка на реда на извикванията
InOrder inOrder = inOrder(repository);
inOrder.verify(repository).save(any());
inOrder.verify(repository).flush();
Когато трябва да се провери с кой точно обект е бил извикан методът, се използва ArgumentCaptor. Той улавя стойността на аргумента по време на извикването и позволява проверка на неговите полета поотделно. ArgumentCaptor е особено полезен, когато тестваният код създава обект вътрешно и го предава на зависимостта — не можете да проверите този обект по друг начин.
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());
@Mock и @InjectMocks — две ключови анотации на Mockito, които значително намаляват boilerplate кода. @Mock създава мок за поле, а @InjectMocks инжектира всички мокове от тестовия клас в тествания обект чрез конструктор, сетер или поле.
Механизмът @InjectMocks се опитва да инжектира зависимости в следния ред: конструктор с най-голям брой аргументи, сетер по тип, частно поле. Ако никой начин не работи — обектът остава с null зависимости и тестът се проваля с NullPointerException.
Важно е да се разбере: @InjectMocks не анализира типовете на полетата — поставя всеки мок, съвместим по тип. Ако в класа има две полета от един и същи тип — Mockito може да инжектира грешен мок. В такива случаи се препоръчва използването на изричен конструктор с предаване на мокове.
В Android разработката Mockito се използва заедно с JUnit за тестване на ViewModel, Repository и UseCase. Тъй като тези класове работят на JVM без Android контекст, Mockito замества техните зависимости — Room DAO, Retrofit API, SharedPreferences — със стабове с предвидимо поведение.
За добавяне на Mockito в Android проект е достатъчно да добавите зависимостта mockito-core или mockito-inline (последната поддържа мокване на final класове и статични методи). Версия 5.12.0 (2024) включва поддръжка за Java 21 и подобрена интеграция с JUnit 5.
// build.gradle.kts (модул)
dependencies {
testImplementation("org.mockito:mockito-core:5.12.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.12.0")
}
Преди за мокване на статични методи и конструктори се изискваше PowerMock — разширение, работещо чрез инструментиране на байткод. От Mockito 5.x с mockito-inline тази функционалност е вградена директно: mockStatic(ClassName.class) позволява мокване на статични методи без допълнителни библиотеки.
Типичен сценарий: ViewModel извиква метод на хранилището и трансформира резултата в UI състояние. Mockito замества хранилището, а тестът проверява дали ViewModel правилно обработва успешния отговор и грешката. При използване на Clean Architecture се създават мокове за всеки слой: DataSource, Repository и UseCase — това позволява тестване на всеки слой изолирано.
Често задавани въпроси
Mockito е библиотека за Java и Kotlin, използваща прокси и рефлексия. MockK е Kotlin-first библиотека с поддръжка на корутини, extension функции и final класове без допълнителна конфигурация.
От Mockito 2.1 мокването на final класове се поддържа чрез opt-in. Във версия 5.x (mockito-inline) това е включено по подразбиране. Достатъчно е да добавите зависимостта mockito-inline и да използвате стандартния mock() метод.
Spy е частичен мок, който по подразбиране извиква реални методи, но позволява предефиниране на някои от тях чрез when().thenReturn(). Spy е полезен за тестване на legacy код, когато не можете да пренапишете целия клас.
thenReturn винаги връща същата стойност, независимо от аргументите. thenAnswer изчислява връщаната стойност въз основа на извикването — аргументи, самия мок, състояние. За динамични отговори винаги използвайте thenAnswer.
Verify проверява не само резултата, но и процеса — факта на обръщение към зависимостта. Това е критично за услуги, които трябва да запазват данни или да изпращат уведомления. Без verify тестът няма да открие, че методът не е извикал save() или send().
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също