Android) Jetpack Paging3 유닛 테스트 해보자

Android) Jetpack Paging3 유닛 테스트 해보자

Paging 3

Android Jetpack에서 제공하는 페이징을 위한 라이브러리입니다.

성능, 메모리, 비용 측면에서 효율적입니다.

PagingSource

네트워크 또는 데이터 베이스에서 데이터를 로드하는 추상 클래스. key 타입을 정의하여 구현합니다.

LoadResult 라는 sealed class 에서 응답 처리와 에러 핸들링에 도움을 주기 때문에, 자체적으로 결과 클래스를 만들어 래핑 할 필요가 없습니다.

라는 에서 응답 처리와 에러 핸들링에 도움을 주기 때문에, 자체적으로 결과 클래스를 만들어 래핑 할 필요가 없습니다. 로드할 다음 페이지가 없으면 null을 nextKey에 전달, 이전 페이지가 없으면 prevKey에 전달합니다.

class PicsumPagingSource(private val service: PicsumService) : PagingSource() { override suspend fun load(params: LoadParams): LoadResult { return try { val page = params.key ?: 0 val items = service.getImages(page, 10) LoadResult.Page( data = items, prevKey = if (page == 0) null else page - 1, nextKey = if (items.isEmpty()) null else page + 1 ) } catch (e: Exception) { LoadResult.Error(e) } } }

Paging Source를 테스트를 하기 전에 필요한 코드들을 선언해 줬습니다.

InstantTaskExecutorRule 은 작업을 동일한 스레드에서 동기적으로 처리하게 해줍니다.

은 작업을 동일한 스레드에서 동기적으로 처리하게 해줍니다. 코루틴을 사용하기 때문에 TestCoroutineDispathcer를 사용합니다.

@ExperimentalCoroutinesApi class PicsumPagingSourceTest { @get:Rule val mainCoroutineRule = MainCoroutineRule() @get:Rule val instanceExecutorRule = InstantTaskExecutorRule() private val service: PicsumService = mockk() private lateinit var picsumPagingSource: PicsumPagingSource @Before fun setUp() { picsumPagingSource = PicsumPagingSource(service) }

이벤트를 발생시키는 key는 LoadPrarms입니다. LoadPrarms는 seald class이며 3 가지의 타입이 있습니다.

LoadPrarms는 seald class이며 3 가지의 타입이 있습니다. Refersh - 초기 로드 또는 새로고침.

- 초기 로드 또는 새로고침. Append - 데이터 페이지를 로드하고 목록의 끝에 추가.

- 데이터 페이지를 로드하고 목록의 끝에 추가. Prepend - 데이터 페이지를 로드하고 목록의 시작 부분에 추가.

Paging Source - failure / error 테스트

@Test fun `paging source load failure received io exception`() = mainCoroutineRule.runBlockingTest { val error = IOException("404", Throwable()) coEvery { service.getImages(any(), any()) } throws error val expectedResult = PagingSource.LoadResult.Error(error) Assert.assertEquals( expectedResult, picsumPagingSource.load( PagingSource.LoadParams.Refresh( key = 0, loadSize = 1, placeholdersEnabled = false ) ) ) } @Test fun `paging source load failure received null exception`() = mainCoroutineRule.runBlockingTest { coEvery { service.getImages(any(), any()) } throws NullPointerException() val expectedResult = PagingSource.LoadResult.Error(NullPointerException()) Assert.assertEquals( expectedResult.toString(), picsumPagingSource.load( PagingSource.LoadParams.Refresh( key = 0, loadSize = 1, placeholdersEnabled = false ) ).toString() ) }

Paigng Source - refresh 테스트

@Test fun `paging source refresh received success`() = mainCoroutineRule.runBlockingTest { coEvery { service.getImages(any(), any()) } returns mockItemList service.getImages(0, 10) coVerify { service.getImages(any(), any()) } val expectedResult = PagingSource.LoadResult.Page(data = mockItemList, prevKey = null, nextKey = 1) Assert.assertEquals( expectedResult, picsumPagingSource.load( PagingSource.LoadParams.Refresh( key = 0, loadSize = 10, placeholdersEnabled = false ) ) ) }

Paigng Source - append 테스트

@Test fun `paging source append received success`() = mainCoroutineRule.runBlockingTest { coEvery { service.getImages(any(), any()) } returns mockItemList service.getImages(0, 10) coVerify { service.getImages(any(), any()) } val expectedResult = PagingSource.LoadResult.Page(data = mockItemList, prevKey = 0, nextKey = 2) Assert.assertEquals( expectedResult, picsumPagingSource.load( PagingSource.LoadParams.Append( key = 1, loadSize = 10, placeholdersEnabled = false ) ) ) }

Paging Source - prepend 테스트

@Test fun `paging source prepend received success`() = mainCoroutineRule.runBlockingTest { coEvery { service.getImages(any(), any()) } returns mockItemList service.getImages(0, 10) coVerify { service.getImages(any(), any()) } val expectedResult = PagingSource.LoadResult.Page(data = mockItemList, prevKey = null, nextKey = 1) Assert.assertEquals( expectedResult, picsumPagingSource.load( PagingSource.LoadParams.Prepend( key = 0, loadSize = 10, placeholdersEnabled = false ) ) ) }

RemoteMediator

네트워크 및 로컬 데이터베이스에서 페이징 데이터를 로드하는 역할.

로컬 데이터베이스를 데이터 소스로 활용함으로써, 네트워크 연결이 불안정할 시 좋은 방법.

load() 메서드가 정확한 MediatorResult를 반환하는지 테스트해봅니다.

@ExperimentalPagingApi class PicsumRemoteMediator( private val service: PicsumService, private val database: PicsumDatabase ) : RemoteMediator() { ... }

테스트에 필요한 초기 작업을 해줍니다. 데이터베이스를 테스트하기 편하게 인메모리 방식으로, 프로세스 종료 시에 사라지는 메모리 형태로 구현해 줍니다.

방식으로, 프로세스 종료 시에 사라지는 메모리 형태로 구현해 줍니다. Room 데이터 베이스르 만들기 위해서는 Context가 필요하기 때문에, AndroidJUnit4를 빌드하여 애플리케이션 콘텍스트에 접근할 수 있게 해야 합니다.

@ExperimentalCoroutinesApi @ExperimentalPagingApi @RunWith(AndroidJUnit4::class) class PicsumRemoteMediatorTest { private lateinit var db: PicsumDatabase private val service: PicsumService = mockk() private lateinit var picsumRemoteMediator: PicsumRemoteMediator @Before fun setUp() { val context = ApplicationProvider.getApplicationContext() db = Room.inMemoryDatabaseBuilder(context, PicsumDatabase::class.java).build() picsumRemoteMediator = PicsumRemoteMediator( service, db ) } @After fun closeDb() { db.close() }

해당 테스트는 성공적인 응답을 반환하지만, 반환된 데이터가 비어있는 경우입니다.

load() 메서드는 MediaResult.Success를 반환하고, endOfPaginationReached 속성은 true입니다.

@Test fun refreshLoadReturnsSuccessResult() = runBlocking { coEvery { service.getImages(0, 10) } returns listOf() val pagingState = PagingState( listOf(), null, PagingConfig(10), 10 ) val result = picsumRemoteMediator.load(LoadType.REFRESH, pagingState) Assert.assertTrue(result is RemoteMediator.MediatorResult.Success) Assert.assertTrue((result as RemoteMediator.MediatorResult.Success).endOfPaginationReached) }

Preference

Paging 구현 테스트

How to test Paging 3

반응형

from http://yoon-dailylife.tistory.com/121 by ccl(A) rewrite - 2021-11-28 19:01:46