Chapters

Hide chapters

Android Test-Driven Development by Tutorials

First Edition · Android 10 · Kotlin 1.3 · AS 3.5

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section II: Testing on a New Project

Section 2: 8 chapters
Show chapters Hide chapters

Section III: TDD on Legacy Projects

Section 3: 9 chapters
Show chapters Hide chapters

18. Testing Around Other Components
Written by Lance Gleason

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Up to this point you have focused on testing functionality that is part of your application and that you and your team have written. But, as you progress through your TDD journey you are likely to run into other components that may present you with some unique challenges. These generally fall into one of three categories:

  1. Testable: These are components that can be easily tested and/or verified, so no problems here.
  2. Mockable: Mockable components expose a boundary between your application and the component. Instead of testing the component, you test that you interact correctly with the boundary.
  3. Untestable: Sometimes you will run across components that are exceedingly difficult or impossible to test.

The testable

Some components you use have been designed so that they can be tested, or have end-state values that make it easy to test with them in the mix. For example, in Chapter 9, “Testing the Persistence Layer,” you learned about how to test the persistence layer in your application tests.

Some examples of Android components that are testable include:

  • Local persistence, such at MySQL and Realm-based data stores.
  • Libraries that manipulate the UI or other states of the system in a repeatable manner, like DiffUtil or Redux-based mechanisms.
  • Libraries that have testing hooks built-in.

The mockable

At times you will run across circumstances where your test needs to cross a system boundary that requires mocking. For example, in Chapter 10, “Testing the Network Layer,” the MockWebServer you are using is mocking out your okhttp calls that are made to a server. In this case, the mocking was taken care of for you. In other instances you will need to mock out system boundaries manually.

Permissions

Another common scenario is when working with a component that requires permissions to test. As a quick review, there are three types of Android permissions:

private fun setupPhonNumberOnClick(){
  fragmentViewCompanionBinding.telephone.setOnClickListener {
    // 1
    Dexter.withActivity(activity)
      .withPermission(Manifest.permission.CALL_PHONE)
      .withListener(
        object : PermissionListener {
          // 3
          override fun onPermissionGranted(
            response: PermissionGrantedResponse
          ) {/* ... */
            val intent = Intent(Intent.ACTION_CALL, Uri.parse(
              "tel:" + viewCompanionViewModel.telephone))
            startActivity(intent)
          }

          override fun onPermissionDenied(
            response: PermissionDeniedResponse
          ) {/* ... */}

          // 2
          override fun onPermissionRationaleShouldBeShown(
            permission: PermissionRequest,
            token: PermissionToken
          ) {/* ... */
            token.continuePermissionRequest()
          }

        }
      )
      .onSameThread()
      .check()
  }
}
@get:Rule
val grantPermissionRule: GrantPermissionRule =
  GrantPermissionRule.grant(
    android.Manifest.permission.CALL_PHONE)
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
@get:Rule
val intentsTestRule = IntentsTestRule(MainActivity::class.java)
@Test
fun verify_that_tapping_on_phone_number_dials_phone() {
  // 1
  val intent = Intent()
  val result =
    Instrumentation.ActivityResult(Activity.RESULT_OK, intent)

  intending(
    allOf(
      hasAction(Intent.ACTION_CALL)
    )
  ).respondWith(result)
  // 2
  find_and_select_kevin_in_30318()
  onView(withText("(706) 236-4537")).perform(click())
  // 3
  intended(allOf(hasAction(Intent.ACTION_CALL),
    hasData("tel:(706) 236-4537")))
}

@get:Rule
val grantPermissionRule: GrantPermissionRule =
  GrantPermissionRule.grant(
    android.Manifest.permission.CALL_PHONE)

@get:Rule
val intentsTestRule = IntentsTestRule(MainActivity::class.java)
@Test
fun verify_that_tapping_on_phone_number_dials_phone() {
  // 1
  val intent = Intent()
  val result =
    Instrumentation.ActivityResult(Activity.RESULT_OK, intent)

  Intents.intending(
    CoreMatchers.allOf(
      IntentMatchers.hasAction(Intent.ACTION_CALL)
    )
  ).respondWith(result)
  // 2
  onView(withText("(706) 236-4537"))
    .perform(ViewActions.click())
  // 3
  Intents.intended(
    CoreMatchers.allOf(
      IntentMatchers.hasAction(Intent.ACTION_CALL),
      IntentMatchers.hasData("tel:(706) 236-4537")
    )
  )
}

Other mockable components

There are many classes of external components where your best testing strategy will be to mock out your interaction with them. Some good candidates for this include:

The untestable

There are some components where the best TDD option is to not test it. Determining that the component is untestable can be tricky. TDD is hard. On one hand you don’t want to give up too soon on testing something. On the other hand you don’t want to spend too much time trying to test the untestable.

Google maps Android SDK

Google Maps Android SDK https://developers.google.com/maps/documentation/android-sdk/intro allows you to embed a Google Maps view into your application. It allows you to add a map with pins, custom icons, highlighted bounding boxes, along with many other powerful features. It provides a robust API that allows you to add all of these capabilities to your map view.

System setting API calls

Imagine you have an app that needs to get the values for some system attributes on your device such as the device’s IP address, SIM card provider and current GPS location. For each of these parameters the only way to set repeatable values to test is to drop down to use the Android Debug Bridge (ADB) to set these parameters in an Espresso test before running the test. While this can be done in unit tests, it can be a problematic solution for multiple reasons including:

Key points

  • Testable components have hooks or inputs and outputs that can be validated.
  • Many components are best tested by mocking your interaction with them.
  • It is possible to test dangerous permissions.
  • There are some components that are untestable.
  • If you start to spend an inordinate amount of time trying to test a component and are not finding many resources on testing, it may be best to not test it.

Where to go from here?

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now