Sayfalandırma uygulamanızı test edin

Uygulamanızda Sayfalandırma kitaplığını kullanırken güçlü bir test stratejisidir. Aşağıdaki gibi veri yükleme bileşenlerini test etmeniz gerekir: PagingSource ve RemoteMediator emin olmak için testler yapın. Ayrıca tüm müşterilere Sayfalama uygulamanızdaki tüm bileşenlerin düzgün çalıştığını doğrulayın bir arada kullanma imkanınız da var.

Bu kılavuzda, Sayfalama kitaplığının farklı uygulamanızın mimari katmanlarını ve nasıl yazacağınızı uçtan uca testler sunar.

Kullanıcı arayüzü katman testleri

Sayfalama kitaplığıyla getirilen veriler, kullanıcı arayüzünde şu şekilde tüketilir: Flow<PagingData<Value>>. Kullanıcı arayüzündeki verilerin beklediğiniz gibi olduğunu doğrulamak amacıyla test yazmak için paging-testing bağımlılık. Flow<PagingData<Value>> öğesinde asSnapshot() uzantısını içeriyor. Google lambda alıcısında, alay kaydırmasına olanak tanıyan API'ler sunar etkileşimleridir. Kaydırma tarafından üretilen standart bir List<Value> döndürür. bir sahte etkileşim modelidir. Bu şekilde, verilerinizin bu etkileşimlerin oluşturduğu beklenen öğeleri içerir. Bu, aşağıdaki snippet'te gösterilmektedir:

fun test_items_contain_one_to_ten() = runTest {
  // Get the Flow of PagingData from the ViewModel under test
  val items: Flow<PagingData<String>> = viewModel.items

  val itemsSnapshot: List<String> = items.asSnapshot {
    // Scroll to the 50th item in the list. This will also suspend till
    // the prefetch requirement is met if there's one.
    // It also suspends until all loading is complete.
    scrollTo(index = 50)
  }

  // With the asSnapshot complete, you can now verify that the snapshot
  // has the expected values
  assertEquals(
    expected = (0..50).map(Int::toString),
    actual = itemsSnapshot
  )
}

Alternatif olarak, aşağıdaki snippet'i içerir:


fun test_footer_is_visible() = runTest {
  // Get the Flow of PagingData from the ViewModel under test
  val items: Flow<PagingData<String>> = viewModel.items

  val itemsSnapshot: List<String> = items.asSnapshot {
    // Scroll till the footer is visible
    appendScrollWhile {  item: String -> item != "Footer" }
  }

Dönüşümleri test etme

Uyguladığınız tüm dönüşümleri kapsayan birim testleri de yazmanız gerekir. PagingData akışı. asPagingSourceFactory kullanın uzantısına sahip olur. Bu uzantı aşağıdaki veri türlerinde kullanılabilir:

  • List<Value>.
  • Flow<List<Value>>.

Hangi uzantının kullanılacağı seçimi, neyi test etmeye çalıştığınıza bağlıdır. Şunu kullan:

  • List<Value>.asPagingSourceFactory(): Statik öğeleri test etmek istiyorsanız dönüşümler (ör. map() ve insertSeparators() gibi)
  • Flow<List<Value>>.asPagingSourceFactory(): Güncellemelerin nasıl yedek veri kaynağına yazma gibi verileriniz sayfalamanızı etkiler ardışık düzendir.
ziyaret edin.

Bu uzantılardan herhangi birini kullanmak için aşağıdaki şablonu izleyin:

  • Size uygun uzantıyı kullanarak PagingSourceFactory gerekiyor.
  • Döndürülen PagingSourceFactory değerini şurada kullanın: sahte Repository cihazınız için.
  • Repository hesabını ViewModel cihazınıza iletin.

Ardından ViewModel, önceki bölümde anlatıldığı şekilde test edilebilir. Aşağıdaki ViewModel göz önünde bulundurulmalıdır:

class MyViewModel(
  myRepository: myRepository
) {
  val items = Pager(
    config: PagingConfig,
    initialKey = null,
    pagingSourceFactory = { myRepository.pagingSource() }
  )
  .flow
  .map { pagingData ->
    pagingData.insertSeparators<String, String> { before, _ ->
      when {
        // Add a dashed String separator if the prior item is a multiple of 10
        before.last() == '0' -> "---------"
        // Return null to avoid adding a separator between two items.
        else -> null
      }
  }
}

MyViewModel içindeki dönüşümü test etmek için sahte bir Oluşturulacak verileri temsil eden statik bir List öğesine yetki veren MyRepository aşağıdaki snippet'te gösterildiği gibi dönüştürülür:

class FakeMyRepository(): MyRepository {
    private val items = (0..100).map(Any::toString)

    private val pagingSourceFactory = items.asPagingSourceFactory()

    val pagingSource = pagingSourceFactory()
}

Ardından aşağıdaki snippet'te olduğu gibi ayırıcı mantığı için bir test yazabilirsiniz:

fun test_separators_are_added_every_10_items() = runTest {
  // Create your ViewModel
  val viewModel = MyViewModel(
    myRepository = FakeMyRepository()
  )
  // Get the Flow of PagingData from the ViewModel with the separator transformations applied
  val items: Flow<PagingData<String>> = viewModel.items
                  
  val snapshot: List<String> = items.asSnapshot()

  // With the asSnapshot complete, you can now verify that the snapshot
  // has the expected separators.
}

Veri katmanı testleri

Veri katmanınızdaki bileşenlere yönelik birim testleri yazarak Veri kaynaklarınızdaki verileri uygun şekilde yükleyebilirsiniz. Sağlama sahte versiyonları teste tabi bileşenlerin doğru şekilde çalıştığını doğrulamak için bağımlılıkları izole ediliyor. Depo katmanında test etmeniz gereken ana bileşenler PagingSource ve RemoteMediator. İlerleyen bölümlerde yer alan örnekler, Ağ ile Çağrı Yapma Örnek.

PagingSource testleri

PagingSource uygulamanız için birim testleri, PagingSource örneği ve bu örnekten veri TestPager ile yükleme.

PagingSource örneğini test etmek üzere ayarlamak için şuraya sahte veriler sağlayın: kurucusu. Bu sayede testlerinizdeki veriler üzerinde kontrol sahibi olabilirsiniz. Aşağıdaki örnekte, RedditApi parametresi bir Retrofit sunucu isteklerini ve yanıt sınıflarını tanımlayan bir arayüz oluşturur. Sahte bir sürüm, arayüzü uygulayabilir, gerekli işlevleri geçersiz kılabilir, ve sahte nesnenin nasıl tepki vermesi gerektiğini yapılandırmak için kolaylık yöntemleri sunar. yardımcı olur.

Sahte öğeler uygulandıktan sonra bağımlılıkları ayarlayın ve Testte PagingSource nesne bulundu. Aşağıdaki örnekte, FakeRedditApi nesnesini test yayınları listesiyle başlatma ve test RedditPagingSource örneği:

class SubredditPagingSourceTest {
  private val mockPosts = listOf(
    postFactory.createRedditPost(DEFAULT_SUBREDDIT),
    postFactory.createRedditPost(DEFAULT_SUBREDDIT),
    postFactory.createRedditPost(DEFAULT_SUBREDDIT)
  )
  private val fakeApi = FakeRedditApi().apply {
    mockPosts.forEach { post -> addPost(post) }
  }

  @Test
  fun loadReturnsPageWhenOnSuccessfulLoadOfItemKeyedData() = runTest {
    val pagingSource = RedditPagingSource(
      fakeApi,
      DEFAULT_SUBREDDIT
    )

    val pager = TestPager(CONFIG, pagingSource)

    val result = pager.refresh() as LoadResult.Page

    // Write assertions against the loaded data
    assertThat(result.data)
    .containsExactlyElementsIn(mockPosts)
    .inOrder()
  }
}

TestPager, aşağıdakileri yapmanıza da olanak tanır:

  • PagingSource cihazınızdan art arda yüklemeleri test edin:
    @Test
    fun test_consecutive_loads() = runTest {

      val page = with(pager) {
        refresh()
        append()
        append()
      } as LoadResult.Page

      assertThat(page.data)
      .containsExactlyElementsIn(testPosts)
      .inOrder()
    }
  • PagingSource içinde hata senaryolarını test edin:
    @Test
    fun refresh_returnError() {
        val pagingSource = RedditPagingSource(
          fakeApi,
          DEFAULT_SUBREDDIT
        )
        // Configure your fake to return errors
        fakeApi.setReturnsError()
        val pager = TestPager(CONFIG, source)

        runTest {
            source.errorNextLoad = true
            val result = pager.refresh()
            assertTrue(result is LoadResult.Error)

            val page = pager.getLastLoadedPage()
            assertThat(page).isNull()
        }
    }

RemoteMediator testleri

RemoteMediator birim testlerinin amacı, load() işlevi doğru MediatorResult. Veritabanına eklenen veriler gibi yan etki testleri, entegrasyon testleri için daha uygundur.

İlk adım, RemoteMediator metriğinizin hangi bağımlılıkları bazı ipuçları vereceğim. Aşağıdaki örnekte RemoteMediator gösterilmektedir Oda veritabanı, Güçlendirme arayüzü ve arama gerektiren uygulama dize:

Kotlin

@OptIn(ExperimentalPagingApi::class)
class PageKeyedRemoteMediator(
  private val db: RedditDb,
  private val redditApi: RedditApi,
  private val subredditName: String
) : RemoteMediator<Int, RedditPost>() {
  ...
}

Java

public class PageKeyedRemoteMediator
  extends RxRemoteMediator<Integer, RedditPost> {

  @NonNull
  private RedditDb db;
  @NonNull
  private RedditPostDao postDao;
  @NonNull
  private SubredditRemoteKeyDao remoteKeyDao;
  @NonNull
  private RedditApi redditApi;
  @NonNull
  private String subredditName;

  public PageKeyedRemoteMediator(
    @NonNull RedditDb db,
    @NonNull RedditApi redditApi,
    @NonNull String subredditName
  ) {
      this.db = db;
      this.postDao = db.posts();
      this.remoteKeyDao = db.remoteKeys();
      this.redditApi = redditApi;
      this.subredditName = subredditName;
      ...
  }
}

Java

public class PageKeyedRemoteMediator
  extends ListenableFutureRemoteMediator<Integer, RedditPost> {

  @NonNull
  private RedditDb db;
  @NonNull
  private RedditPostDao postDao;
  @NonNull
  private SubredditRemoteKeyDao remoteKeyDao;
  @NonNull
  private RedditApi redditApi;
  @NonNull
  private String subredditName;
  @NonNull
  private Executor bgExecutor;

  public PageKeyedRemoteMediator(
    @NonNull RedditDb db,
    @NonNull RedditApi redditApi,
    @NonNull String subredditName,
    @NonNull Executor bgExecutor
  ) {
    this.db = db;
    this.postDao = db.posts();
    this.remoteKeyDao = db.remoteKeys();
    this.redditApi = redditApi;
    this.subredditName = subredditName;
    this.bgExecutor = bgExecutor;
    ...
  }
}

Güçlendirme arayüzünü ve arama dizesini PagingSource testleri bölümüne gidin. Modelin sürümünü sağlama çok karmaşık bir süreçtir; bu nedenle her bir veri tabanının bellek içi uygulanması tam sürüm yerine veritabanını kullanabilirsiniz. Çünkü Oda veritabanı oluşturmak Context nesnesi gerektirirse bu RemoteMediator testini androidTest dizinine yerleştirin ve yürütün kullanarak bir test uygulamasına erişebilmesini sağlamak için bağlam. Araçlı testler hakkında daha fazla bilgi için Araçlarlı testler derleme birim testlerini kullanın.

Testler arasında durum sızıntısı olmaması için sökme fonksiyonlarını tanımlama işlevlerine dahildir. Bu yaklaşım, test çalıştırmaları arasında tutarlı sonuçlar sağlar.

Kotlin

@ExperimentalPagingApi
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class PageKeyedRemoteMediatorTest {
  private val postFactory = PostFactory()
  private val mockPosts = listOf(
    postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT),
    postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT),
    postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT)
  )
  private val mockApi = mockRedditApi()

  private val mockDb = RedditDb.create(
    ApplicationProvider.getApplicationContext(),
    useInMemory = true
  )

  @After
  fun tearDown() {
    mockDb.clearAllTables()
    // Clear out failure message to default to the successful response.
    mockApi.failureMsg = null
    // Clear out posts after each test run.
    mockApi.clearPosts()
  }
}

Java

@RunWith(AndroidJUnit4.class)
public class PageKeyedRemoteMediatorTest {
  static PostFactory postFactory = new PostFactory();
  static List<RedditPost> mockPosts = new ArrayList<>();
  static MockRedditApi mockApi = new MockRedditApi();
  private RedditDb mockDb = RedditDb.Companion.create(
    ApplicationProvider.getApplicationContext(),
    true
  );

  static {
    for (int i=0; i<3; i++) {
      RedditPost post = postFactory.createRedditPost(DEFAULT_SUBREDDIT);
      mockPosts.add(post);
    }
  }

  @After
  public void tearDown() {
    mockDb.clearAllTables();
    // Clear the failure message after each test run.
    mockApi.setFailureMsg(null);
    // Clear out posts after each test run.
    mockApi.clearPosts();
  }
}

Java

@RunWith(AndroidJUnit4.class)
public class PageKeyedRemoteMediatorTest {
  static PostFactory postFactory = new PostFactory();
  static List<RedditPost> mockPosts = new ArrayList<>();
  static MockRedditApi mockApi = new MockRedditApi();

  private RedditDb mockDb = RedditDb.Companion.create(
    ApplicationProvider.getApplicationContext(),
    true
  );

  static {
    for (int i=0; i<3; i++) {
      RedditPost post = postFactory.createRedditPost(DEFAULT_SUBREDDIT);
      mockPosts.add(post);
    }
  }

  @After
  public void tearDown() {
    mockDb.clearAllTables();
    // Clear the failure message after each test run.
    mockApi.setFailureMsg(null);
    // Clear out posts after each test run.
    mockApi.clearPosts();
  }
}

Bir sonraki adım, load() işlevini test etmektir. Bu örnekte iki etkin test edilecek durumlar:

  • İlk durum, mockApi ürününün geçerli veriler döndürdüğü zamandır. load() işlevi MediatorResult.Success ve endOfPaginationReached değerini döndürmelidir özelliği false olmalıdır.
  • İkinci durum, mockApi adlı kullanıcının başarılı bir yanıt döndürmesi ancak döndürülen veriler boştur. load() işlevi, MediatorResult.Success ve endOfPaginationReached özelliği true.
  • Üçüncü durum ise mockApi öğesinin veri getirirken istisna yapmasıdır. load() işlevi, MediatorResult.Error değerini döndürmelidir.

İlk durumu test etmek için aşağıdaki adımları uygulayın:

  1. Döndürülecek yayın verileriyle mockApi özelliğini ayarlayın.
  2. RemoteMediator nesnesini başlatın.
  3. load() işlevini test edin.

Kotlin

@Test
fun refreshLoadReturnsSuccessResultWhenMoreDataIsPresent() = runTest {
  // Add mock results for the API to return.
  mockPosts.forEach { post -> mockApi.addPost(post) }
  val remoteMediator = PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT
  )
  val pagingState = PagingState<Int, RedditPost>(
    listOf(),
    null,
    PagingConfig(10),
    10
  )
  val result = remoteMediator.load(LoadType.REFRESH, pagingState)
  assertTrue { result is MediatorResult.Success }
  assertFalse { (result as MediatorResult.Success).endOfPaginationReached }
}

Java

@Test
public void refreshLoadReturnsSuccessResultWhenMoreDataIsPresent()
  throws InterruptedException {

  // Add mock results for the API to return.
  for (RedditPost post: mockPosts) {
    mockApi.addPost(post);
  }

  PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT
  );
  PagingState<Integer, RedditPost> pagingState = new PagingState<>(
    new ArrayList(),
    null,
    new PagingConfig(10),
    10
  );
  remoteMediator.loadSingle(LoadType.REFRESH, pagingState)
    .test()
    .await()
    .assertValueCount(1)
    .assertValue(value -> value instanceof RemoteMediator.MediatorResult.Success &&
      ((RemoteMediator.MediatorResult.Success) value).endOfPaginationReached() == false);
}

Java

@Test
public void refreshLoadReturnsSuccessResultWhenMoreDataIsPresent()
  throws InterruptedException, ExecutionException {

  // Add mock results for the API to return.
  for (RedditPost post: mockPosts) {
    mockApi.addPost(post);
  }

  PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT,
    new CurrentThreadExecutor()
  );
  PagingState<Integer, RedditPost> pagingState = new PagingState<>(
    new ArrayList(),
    null,
    new PagingConfig(10),
    10
  );

  RemoteMediator.MediatorResult result =
    remoteMediator.loadFuture(LoadType.REFRESH, pagingState).get();

  assertThat(result, instanceOf(RemoteMediator.MediatorResult.Success.class));
  assertFalse(((RemoteMediator.MediatorResult.Success) result).endOfPaginationReached());
}

İkinci test, mockApi işlevinin boş sonuç döndürmesini gerektirir. Çünkü siz her test çalıştırmasından sonra mockApi verilerini temizlediğinde boş bir değer döndürecektir varsayılan olarak belirlemenize olanak tanır.

Kotlin

@Test
fun refreshLoadSuccessAndEndOfPaginationWhenNoMoreData() = runTest {
  // To test endOfPaginationReached, don't set up the mockApi to return post
  // data here.
  val remoteMediator = PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT
  )
  val pagingState = PagingState<Int, RedditPost>(
    listOf(),
    null,
    PagingConfig(10),
    10
  )
  val result = remoteMediator.load(LoadType.REFRESH, pagingState)
  assertTrue { result is MediatorResult.Success }
  assertTrue { (result as MediatorResult.Success).endOfPaginationReached }
}

Java

@Test
public void refreshLoadSuccessAndEndOfPaginationWhenNoMoreData()
  throws InterruptedException() {

  // To test endOfPaginationReached, don't set up the mockApi to return post
  // data here.
  PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT
  );
  PagingState<Integer, RedditPost> pagingState = new PagingState<>(
    new ArrayList(),
    null,
    new PagingConfig(10),
    10
  );
  remoteMediator.loadSingle(LoadType.REFRESH, pagingState)
    .test()
    .await()
    .assertValueCount(1)
    .assertValue(value -> value instanceof RemoteMediator.MediatorResult.Success &&
      ((RemoteMediator.MediatorResult.Success) value).endOfPaginationReached() == true);
}

Java

@Test
public void refreshLoadSuccessAndEndOfPaginationWhenNoMoreData()
  throws InterruptedException, ExecutionException {

  // To test endOfPaginationReached, don't set up the mockApi to return post
  // data here.
  PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT,
    new CurrentThreadExecutor()
  );
  PagingState<Integer, RedditPost> pagingState = new PagingState<>(
    new ArrayList(),
    null,
    new PagingConfig(10),
    10
  );

  RemoteMediator.MediatorResult result =
    remoteMediator.loadFuture(LoadType.REFRESH, pagingState).get();

  assertThat(result, instanceOf(RemoteMediator.MediatorResult.Success.class));
  assertTrue(((RemoteMediator.MediatorResult.Success) result).endOfPaginationReached());
}

Son testte, testin yapılabilmesi için mockApi öğesinin bir istisna yapması gerekir. load() işlevinin MediatorResult.Error değerini doğru şekilde döndürdüğünü doğrulayın.

Kotlin

@Test
fun refreshLoadReturnsErrorResultWhenErrorOccurs() = runTest {
  // Set up failure message to throw exception from the mock API.
  mockApi.failureMsg = "Throw test failure"
  val remoteMediator = PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT
  )
  val pagingState = PagingState<Int, RedditPost>(
    listOf(),
    null,
    PagingConfig(10),
    10
  )
  val result = remoteMediator.load(LoadType.REFRESH, pagingState)
  assertTrue {result is MediatorResult.Error }
}

Java

@Test
public void refreshLoadReturnsErrorResultWhenErrorOccurs()
  throws InterruptedException {

  // Set up failure message to throw exception from the mock API.
  mockApi.setFailureMsg("Throw test failure");
  PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT
  );
  PagingState<Integer, RedditPost> pagingState = new PagingState<>(
    new ArrayList(),
    null,
    new PagingConfig(10),
    10
  );
  remoteMediator.loadSingle(LoadType.REFRESH, pagingState)
    .test()
    .await()
    .assertValueCount(1)
    .assertValue(value -> value instanceof RemoteMediator.MediatorResult.Error);
}

Java

@Test
public void refreshLoadReturnsErrorResultWhenErrorOccurs()
  throws InterruptedException, ExecutionException {

  // Set up failure message to throw exception from the mock API.
  mockApi.setFailureMsg("Throw test failure");
  PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT,
    new CurrentThreadExecutor()
  );
  PagingState<Integer, RedditPost> pagingState = new PagingState<>(
    new ArrayList(),
    null,
    new PagingConfig(10),
    10
  );
  RemoteMediator.MediatorResult result =
    remoteMediator.loadFuture(LoadType.REFRESH, pagingState).get();

  assertThat(result, instanceOf(RemoteMediator.MediatorResult.Error.class));
}

Uçtan uca testler

Birim testleri, bağımsız Sayfalandırma bileşenlerinin uçtan uca testler de uygulamanızın kullanıcı deneyimine bir bütün olarak çalışır. Bu testler için bazı örnek bağımlılıklar gerekli olacaktır ancak genellikle uygulama kodunuzun büyük bölümünü kaplar.

Bu bölümdeki örnekte ağından yararlanırız. Örnek API, tutarlı bir test kümesi döndürecek şekilde yapılandırılmıştır Bu da testlerin tekrarlanabilir olmasına yol açar. Hangi bağımlılıklarla değiştireceğinize karar verin yaptıklarına, ne kadar tutarlı olduğuna, ne kadar tutarlı olduklarına ve testlerinizin doğruluk oranını bilmeniz önemlidir.

Kodunuzu, Google Haberler'deki uygulamanızın taklidiyle kolayca değiştirmenizi sağlayacak şekilde yazın ve bildirmeyi konuştuk. Aşağıdaki örnekte temel bir hizmet bulucu kullanılmaktadır etkili bir yoludur. ve gerektiğinde bağımlılıkları değiştirebilirsiniz. Daha büyük uygulamalarda, bağımlılık yerleştirme Hilt gibi bir kütüphane proje yönetiminde ve daha karmaşık bağımlılık grafikleri oluşturabilirsiniz.

Kotlin

class RedditActivityTest {

  companion object {
    private const val TEST_SUBREDDIT = "test"
  }

  private val postFactory = PostFactory()
  private val mockApi = MockRedditApi().apply {
    addPost(postFactory.createRedditPost(DEFAULT_SUBREDDIT))
    addPost(postFactory.createRedditPost(TEST_SUBREDDIT))
    addPost(postFactory.createRedditPost(TEST_SUBREDDIT))
  }

  @Before
  fun init() {
    val app = ApplicationProvider.getApplicationContext<Application>()
    // Use a controlled service locator with a mock API.
    ServiceLocator.swap(
      object : DefaultServiceLocator(app = app, useInMemoryDb = true) {
        override fun getRedditApi(): RedditApi = mockApi
      }
    )
  }
}

Java

public class RedditActivityTest {

  public static final String TEST_SUBREDDIT = "test";

  private static PostFactory postFactory = new PostFactory();
  private static MockRedditApi mockApi = new MockRedditApi();

  static {
    mockApi.addPost(postFactory.createRedditPost(DEFAULT_SUBREDDIT));
    mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT));
    mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT));
  }

  @Before
  public void setup() {
    Application app = ApplicationProvider.getApplicationContext();
    // Use a controlled service locator with a mock API.
    ServiceLocator.Companion.swap(
      new DefaultServiceLocator(app, true) {
        @NotNull
        @Override
        public RedditApi getRedditApi() {
          return mockApi;
        }
      }
    );
  }
}

Java

public class RedditActivityTest {

  public static final String TEST_SUBREDDIT = "test";

  private static PostFactory postFactory = new PostFactory();
  private static MockRedditApi mockApi = new MockRedditApi();

  static {
    mockApi.addPost(postFactory.createRedditPost(DEFAULT_SUBREDDIT));
    mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT));
    mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT));
  }

  @Before
  public void setup() {
    Application app = ApplicationProvider.getApplicationContext();
    // Use a controlled service locator with a mock API.
    ServiceLocator.Companion.swap(
      new DefaultServiceLocator(app, true) {
        @NotNull
        @Override
        public RedditApi getRedditApi() {
          return mockApi;
        }
      }
    );
  }
}

Test yapısını oluşturduktan sonraki adım, Pager uygulaması doğru olduğunda. Tek bir test, projenizin Pager nesnesi, varsayılan verileri sayfa ilk yüklendiğinde yükler, başka bir öğe ise test, Pager nesnesinin temel alınan ek verileri doğru şekilde yüklediğini doğrulamalıdır kullanıcı girişinde görebilirsiniz. Aşağıdaki örnekte test, Pager nesne, RecyclerView.Adapter öğesini doğru sayıda öğeyle günceller kullanıcı arama yapmak için farklı bir alt reddit girdiğinde API'den döndürülür.

Kotlin

@Test
fun loadsTheDefaultResults() {
    ActivityScenario.launch(RedditActivity::class.java)

    onView(withId(R.id.list)).check { view, noViewFoundException ->
        if (noViewFoundException != null) {
            throw noViewFoundException
        }

        val recyclerView = view as RecyclerView
        assertEquals(1, recyclerView.adapter?.itemCount)
    }
}

@Test
// Verify that the default data is swapped out when the user searches for a
// different subreddit.
fun loadsTheTestResultsWhenSearchingForSubreddit() {
  ActivityScenario.launch(RedditActivity::class.java )

  onView(withId(R.id.list)).check { view, noViewFoundException ->
    if (noViewFoundException != null) {
      throw noViewFoundException
    }

    val recyclerView = view as RecyclerView
    // Verify that it loads the default data first.
    assertEquals(1, recyclerView.adapter?.itemCount)
  }

  // Search for test subreddit instead of default to trigger new data load.
  onView(withId(R.id.input)).perform(
    replaceText(TEST_SUBREDDIT),
    pressKey(KeyEvent.KEYCODE_ENTER)
  )

  onView(withId(R.id.list)).check { view, noViewFoundException ->
    if (noViewFoundException != null) {
      throw noViewFoundException
    }

    val recyclerView = view as RecyclerView
    assertEquals(2, recyclerView.adapter?.itemCount)
  }
}

Java

@Test
public void loadsTheDefaultResults() {
  ActivityScenario.launch(RedditActivity.class);

  onView(withId(R.id.list)).check((view, noViewFoundException) -> {
    if (noViewFoundException != null) {
      throw noViewFoundException;
    }

    RecyclerView recyclerView = (RecyclerView) view;
    assertEquals(1, recyclerView.getAdapter().getItemCount());
  });
}

@Test
// Verify that the default data is swapped out when the user searches for a
// different subreddit.
public void loadsTheTestResultsWhenSearchingForSubreddit() {
  ActivityScenario.launch(RedditActivity.class);

  onView(withId(R.id.list)).check((view, noViewFoundException) -> {
    if (noViewFoundException != null) {
      throw noViewFoundException;
    }

    RecyclerView recyclerView = (RecyclerView) view;
    // Verify that it loads the default data first.
    assertEquals(1, recyclerView.getAdapter().getItemCount());
  });

  // Search for test subreddit instead of default to trigger new data load.
  onView(withId(R.id.input)).perform(
    replaceText(TEST_SUBREDDIT),
    pressKey(KeyEvent.KEYCODE_ENTER)
  );

  onView(withId(R.id.list)).check((view, noViewFoundException) -> {
    if (noViewFoundException != null) {
      throw noViewFoundException;
    }

    RecyclerView recyclerView = (RecyclerView) view;
    assertEquals(2, recyclerView.getAdapter().getItemCount());
  });
}

Java

@Test
public void loadsTheDefaultResults() {
  ActivityScenario.launch(RedditActivity.class);

  onView(withId(R.id.list)).check((view, noViewFoundException) -> {
    if (noViewFoundException != null) {
      throw noViewFoundException;
    }

    RecyclerView recyclerView = (RecyclerView) view;
    assertEquals(1, recyclerView.getAdapter().getItemCount());
  });
}

@Test
// Verify that the default data is swapped out when the user searches for a
// different subreddit.
public void loadsTheTestResultsWhenSearchingForSubreddit() {
  ActivityScenario.launch(RedditActivity.class);

  onView(withId(R.id.list)).check((view, noViewFoundException) -> {
    if (noViewFoundException != null) {
      throw noViewFoundException;
    }

    RecyclerView recyclerView = (RecyclerView) view;
    // Verify that it loads the default data first.
    assertEquals(1, recyclerView.getAdapter().getItemCount());
  });

  // Search for test subreddit instead of default to trigger new data load.
  onView(withId(R.id.input)).perform(
    replaceText(TEST_SUBREDDIT),
    pressKey(KeyEvent.KEYCODE_ENTER)
  );

  onView(withId(R.id.list)).check((view, noViewFoundException) -> {
    if (noViewFoundException != null) {
      throw noViewFoundException;
    }

    RecyclerView recyclerView = (RecyclerView) view;
    assertEquals(2, recyclerView.getAdapter().getItemCount());
  });
}

Araçlı testler, verilerin kullanıcı arayüzünde doğru şekilde görüntülendiğini doğrulamalıdır. Yapılması gerekenler Bunu kontrol etmek için RecyclerView.Adapter veya her bir satırdaki görünüm ve verilerin doğru biçimlendirildiğini doğrulayın.

ziyaret edin.