Какие знаешь способы тестирования Presenter в MVP?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Тестирование Presenter в архитектуре MVP
В архитектуре MVP (Model-View-Presenter) Presenter выступает центральным компонентом, содержащим бизнес-логику, что делает его ключевым объектом для модульного тестирования. Вот основные подходы и техники:
1. Unit-тесты с моками зависимостей
Наиболее распространённый способ — использование мок-объектов (mock objects) для View и Model. Это позволяет изолировать Presenter и тестировать только его логику.
// Пример теста с Mockito
class UserPresenterTest {
private lateinit var presenter: UserPresenter
private lateinit var mockView: UserContract.View
private lateinit var mockRepository: UserRepository
@Before
fun setUp() {
mockView = mock()
mockRepository = mock()
presenter = UserPresenter(mockView, mockRepository)
}
@Test
fun `loadUser should show user data when successful`() {
// Arrange
val testUser = User("John", "john@example.com")
`when`(mockRepository.getUser(anyInt())).thenReturn(testUser)
// Act
presenter.loadUser(123)
// Assert
verify(mockView).showLoading()
verify(mockView).hideLoading()
verify(mockView).displayUser(testUser)
verify(mockView, never()).showError(anyString())
}
@Test
fun `loadUser should show error when repository fails`() {
// Arrange
val errorMessage = "Network error"
`when`(mockRepository.getUser(anyInt())).thenThrow(IOException(errorMessage))
// Act
presenter.loadUser(123)
// Assert
verify(mockView).showLoading()
verify(mockView).hideLoading()
verify(mockView).showError(errorMessage)
verify(mockView, never()).displayUser(any())
}
}
2. Использование интерфейсов и контрактов
Критически важно, чтобы View и Model были представлены интерфейсами. Это позволяет:
- Легко создавать мок-реализации для тестов
- Чётко определять контракты взаимодействия
- Избегать связывания с конкретными реализациями
// Контракт для четкого определения ответственности
interface UserContract {
interface View {
fun showLoading()
fun hideLoading()
fun displayUser(user: User)
fun showError(message: String)
}
interface Presenter {
fun loadUser(userId: Int)
fun onDestroy()
}
}
3. Тестирование реактивных Presenter (RxJava, Coroutines)
Для Presenter с асинхронными операциями требуются специальные подходы:
Для RxJava:
@Test
fun `rxExample should handle emissions correctly`() {
// Arrange
val testObserver = TestObserver<User>()
`when`(mockRepository.getUserStream()).thenReturn(Observable.just(testUser))
// Act
presenter.loadUserStream()
.subscribe(testObserver)
// Assert
testObserver.assertValue(testUser)
testObserver.assertComplete()
verify(mockView).onUserStreamStarted()
}
Для Kotlin Coroutines:
@Test
fun `coroutineExample should update view`() = runTest {
// Arrange
val testUser = User("Test", "test@example.com")
coEvery { mockRepository.getUserSuspend() } returns testUser
// Act
presenter.loadUserSuspend()
// Assert
coVerify { mockView.displayUser(testUser) }
}
4. Тестирование жизненного цикла
Presenter часто управляет жизненным циклом, поэтому важно тестировать:
- Привязку и отвязку от View
- Очистку ресурсов в onDestroy()
- Обработку конфигурационных изменений
@Test
fun `onDestroy should clear disposables`() {
// Arrange
val compositeDisposable = spyk(CompositeDisposable())
presenter = UserPresenter(mockView, mockRepository, compositeDisposable)
// Act
presenter.onDestroy()
// Assert
verify(compositeDisposable).clear()
assertTrue(compositeDisposable.isDisposed)
}
5. Интеграционное тестирование Presenter
Хотя модульные тесты преобладают, иногда полезно интеграционное тестирование:
class UserPresenterIntegrationTest {
@Test
fun `presenter with real repository stub`() {
// Использование реальной модели с заглушками данных
val realRepository = UserRepository(
apiService = createMockApiService(),
database = createInMemoryDatabase()
)
val mockView = mock<UserContract.View>()
val presenter = UserPresenter(mockView, realRepository)
// Тестирование полного потока
presenter.loadUser(1)
// Проверка взаимодействия
verify(mockView).displayUser(any())
}
}
6. Использование тестовых правил (Test Rules)
Для упрощения настройки тестовой среды:
@RunWith(MockitoJUnitRunner::class)
class UserPresenterTest {
@Mock lateinit var view: UserContract.View
@Mock lateinit var repository: UserRepository
@InjectMocks lateinit var presenter: UserPresenter
@Rule @JvmField
val schedulerRule = RxImmediateSchedulerRule() // Правило для RxJava
@Test
fun `test with annotations and rules`() {
// Тест получает автоматически инициализированные зависимости
}
}
Ключевые принципы успешного тестирования Presenter:
- Изоляция бизнес-логики от Android-компонентов
- Полное покрытие сценариев: успешные и ошибочные кейсы
- Тестирование асинхронных операций с имитацией задержек
- Проверка всех взаимодействий с View и Model
- Использование соглашения Given-When-Then для структурирования тестов
- Минимизация зависимостей от фреймворков Android
Правильно протестированный Presenter обеспечивает стабильность бизнес-логики, упрощает рефакторинг и позволяет безопасно вносить изменения, так как тесты быстро выявят проблемы в логике обработки данных и взаимодействии с View. Современные библиотеки вроде Mockito, JUnit, Espresso (для интеграционных тестов) и специализированные инструменты для реактивного программирования делают этот процесс эффективным и надёжным.