Skip to content

Testing

Sam Hindmarsh edited this page Apr 1, 2019 · 2 revisions

Due to the strict nature of how matching is done with this new pattern language, all we need to do is ensure that an input URL (in the form of a Uri) matches our pattern definition. For this, we can instantiate our handlers and test their matchers against an input URI in just a few lines of code.

Using Plunge to test deep link handling

Plunge includes two optional, though recommended, dependencies for helping you test your apps deep link handling. These modules allow you to not only test your pattern implementation, but also check to make sure your app actually handles the correct URLs from an intent-filter level.

Defining test cases

Test cases for Plunge are defined in JSON files. This allows Plunge to access these test cases at both a pattern-matching level and intent-filter level in a unified way. A sample test case may look like this:

{
  "url": "https://plunge.example.com/submarines/12345/buy",
  "description": "The page for buying a submarine",
  "params": [
    {
      "name": "id",
      "value": "12345"
    }
  ]
}

Here we define the URL to test, with a human readable description. We also define params, which is what we expect our patterns to match on and extract.

These test cases should be added to a new directory in the test source directory in your app module – for example, src/test/test-cases. See the sample project for our recommended approach.

Using plunge-test for testing patterns

You'll need to ensure you've added the following dependencies to your app's test configuration in the module-level build.gradle (if you've included all the dependencies from the README, you'll have this set up already):

testImplementation 'nz.co.trademe.plunge:plunge-test:current_version'
testImplementation 'nz.co.trademe.plunge:plunge-parsing:current_version'

You'll then need to set up one parameterized unit test to cover all the test cases. An example looks like this (from the README):

@RunWith(ParameterizedRobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class PlungeExampleTests {
  companion object {

    @JvmStatic
    private val pathToTests = System.getProperty("user.dir") + "/src/test/test-cases"

    @JvmStatic
    @ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
    fun parameters() = PlungeTestRunner.testCases(pathToTests)

  }

  val linkHandler = DeepLinkHandler.withSchemeHandlers(
    PlungeExampleSchemeHandler(Mockito.mock(DeepLinkRouter::class.java))
  )

  @ParameterizedRobolectricTestRunner.Parameter(0)
  lateinit var testCase: PlungeTestCase

  @Test
  fun runTest() = PlungeTestRunner.assertPlungeTest(testCase, linkHandler)
}

You can essentially take this example and plug it directly into your project, and just replace the pathToTests and linkHandler with your own test path and DeepLinkRouter respectively (note that the example also depends on Mockito – if you're not using Mockito in your project, you can instantiate a stub object here instead). But let's break down what's going on here anyway. There are 3 main components:

  1. The parameters() function: a static function annotated with @ParameterizedRobolectricTestRunner.Parameters allows the Robolectric test runner to build up the test cases. The provided PlungeTestRunner class has a static function called testCases which processes all the JSON files and returns the collection of test cases in the format the Robolectric test runner expects. You can change the value of name attribute of the Parameters annotation, but note that there is only one object (a PlungeTestCase) in the test case parameters (i.e. only {0} is available to be used). See this section from the JUnit wiki for more info.
  2. The testCase field: a field of type PlungeTestCase, annotated with @ParameterizedRobolectricTestRunner.Parameter(0). This is the field which holds each test case for the parameterized test case to execute against.
  3. The runTest function: a function annotated with @Test. This is a standard JUnit test which runs the current test case using the PlungeTestRunner.assertPlungeTest function. The link handler gets passed in here, and test test cases are run.

Using plunge-gradle-plugin for testing your intent-filters

For this part, you'll need to ensure you've added the following dependencies to your app's test configuration in the module-level build.gradle (if you've included all the dependencies from the README, you'll have all this set up already):

buildscript {
  dependencies {
    classpath "nz.co.trademe.plunge:plunge-gradle-plugin:current_version"
  }
}

dependencies {
  // ...
  testImplementation 'nz.co.trademe.plunge:plunge-parsing:current_version'
}

You'll also need to ensure you apply the plugin and provide a path to your test cases.

apply plugin: nz.co.trademe.plunge

plunge {
    testDirectory = file("$projectDir/src/test/test-cases")
}

After a Gradle sync, you'll notice a few new tasks pop up in the verification section for your app, prefixed with plungeTest. Running these tasks will build and deploy that variant of your app to the device attached, and then run the test cases against that installation of the app to ensure the app handles the URLs you expect it to (but doesn't handle the URLs you don't want your app to handle!).

An example of a positive test case was provided above in the section Defining test cases; a negative case might look like the following:

{
  "url": "https://plunge.example.com/submarines/help/buy",
  "description": "The help page for buying a submarine; we don't support this in the app yet",
  "handled": false
}

There are a couple of subtleties to be aware of when using the Gradle plugin:

  • The release variant of the plungeTest task may not build and deploy your application first. If this is the case, simply ensure you have the release variant installed.
  • For the verification to work, you must ensure only one device is connected in debug mode before running the test. This is due to the fact that it queries a specific device for URLs handled.