Docs

Testing

Test keys, Logcat output, adb commands that open links, the dashboard Debugger and unit tests for your routing.

Use the test environment

Build development versions of your app with a pk_test_ key and create test links for them. Test links, test installs and test events stay out of your live analytics. Switch the dashboard to the test environment to create and inspect them. See Environments.

Remember that resolving follows the key: a live link opened by an app using a pk_test_ key comes back unresolved, and the other way round.

Turn on logging

Lander.initialize(this, apiKey = "pk_test_...", enableLogging = BuildConfig.DEBUG)

The SDK then logs what it does to Logcat at debug level, with the tag Lander. It never logs your API key. Filter for it:

adb logcat -s Lander

Lines you will see:

Log lineMeaning
Created install ID ...First launch (or a reset install). A new random ID was stored.
Init okThe launch report reached Lander. Events can be sent from now on.
Init failed: LanderException(invalid_api_key, 401): ...The key is wrong or revoked.
Deferred link: LanderLink(...)The first launch report returned a deferred link.
The app was opened by a link; ignoring the deferred linkA launch link won over a deferred link.
Resolved LanderLink(...)A link was resolved. The line shows its ID, path, params, URL, confidence and method.
Not a Lander link: https://...Lander answered 404 for this URL. It is still delivered, unresolved.
Resolve failed for https://...: LanderException(...)Network error, timeout, or the link is disabled or expired (410). Delivered unresolved.
Ignoring duplicate link ...The same URL arrived twice within 2 seconds.
Sent 3 eventsA batch of events was accepted.
Event batch failed (network_error), retrying in 2sThe queue will try again with backoff.
Event batch rejected (...): check the API key. Pausing until next launch.A 401 or 403 on events. Events are kept for the next launch.
Install ID belongs to another project, creating a new oneYou switched keys between test and live (or projects). Harmless.

You do not need to tap links in a chat app to test. These commands deliver a URL to your app exactly as a tap would:

# App Link
adb shell am start -a android.intent.action.VIEW \
  -d "https://acme.golander.link/p-123" com.acme.shop

# Custom scheme
adb shell am start -a android.intent.action.VIEW \
  -d "acmeshop://product/123" com.acme.shop

# Cold start: stop the app first
adb shell am force-stop com.acme.shop
adb shell am start -a android.intent.action.VIEW \
  -d "https://acme.golander.link/p-123" com.acme.shop

# Is the domain verified?
adb shell pm get-app-links com.acme.shop

Run each link twice: once with the app in the background (warm start, onNewIntent) and once after stopping it (cold start, onCreate). Both should land on the same screen, once. Rotate the device afterwards: the link must not open again.

Test a first launch

A first launch is what produces a deferred link and an install. Uninstalling, or clearing the app's data, removes the install ID, so the next start is a fresh first launch:

adb uninstall com.acme.shop
# or keep the app installed and wipe its data:
adb shell pm clear com.acme.shop

Ways to see a deferred link while developing:

  • Authenticated handoff (any device): create a handoff with your sk_test_ key for a test user, sign in as that user in the app and call Lander.claimHandoff. See Authenticated handoff.
  • Play Install Referrer: needs a real install from Google Play, for example through an internal testing track. Builds installed from Android Studio or adb install never get a referrer.

Check everything with the Debugger

Open Build > Debugger in the dashboard and paste a link. It checks everything between a tap and your app's screen, in four groups: the link itself, your app configuration (package name and fingerprints), the domain files on the edge (assetlinks.json), and whether an app with the SDK has reported in the last 24 hours. Failed checks link to the fix.

Build > SDK shows the SDK versions that apps report in the current environment (Android apps report as android/0.1.0 in the X-Lander-Sdk header and 0.1.0 as their SDK version), so you can confirm a new build is talking to Lander.

Unit test your routing

LanderLink has a public constructor, so you can test how your app routes links without a device or the network. route, isResolved, params and the other fields are plain Kotlin and work in local JVM tests; only uri needs Android (use Robolectric, or read uriString).

app/src/test/kotlin/RoutingTest.kt
import link.lander.android.LanderConfidence
import link.lander.android.LanderLink
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Test

class RoutingTest {
    @Test
    fun `a resolved link routes to its path`() {
        val link = LanderLink(
            uriString = "https://acme.golander.link/p-123",
            linkId = "0b8f9a52-6f1c-4d3e-9a43-3f0d2b7c1e55",
            url = "https://acme.golander.link/p-123",
            path = "/product/123",
            params = mapOf("productId" to "123"),
            confidence = LanderConfidence.DETERMINISTIC,
            method = "app_link",
        )
        assertEquals("/product/123", link.route)
        assertEquals("product/123", destinationFor(link)) // your own mapping
    }

    @Test
    fun `a custom scheme URL routes without Lander`() {
        val link = LanderLink.unresolved("acmeshop://product/123?color=red")
        assertFalse(link.isResolved)
        assertEquals("/product/123?color=red", link.route)
    }
}

For tests of the code that collects links, inject the flow: take a Flow<LanderLink> parameter (defaulting to Lander.deepLinks) in the class that navigates, and pass a MutableSharedFlow or flowOf(...) in tests.

The example app

The SDK ships with Acme Shop, in sdk/lander_android/example of the Lander repository: an activity with a home screen and a product screen at /product/:id. It shows what Lander resolved for each link, creates share links with createLink and tracks purchases with trackEvent. Its package name is com.acme.shop and its custom scheme is acmeshop, and its setup files are a good reference:

AndroidManifest.xml
kotlin/com/acme/shop/AcmeApp.kt
kotlin/com/acme/shop/MainActivity.kt
java/com/acme/shop/JavaSample.java
AndroidManifest.xml
res/xml/network_security_config.xml

It is set up to run against a local copy of the Lander monorepo (below), using the seeded demo key. The seeded link https://acme.golander.link/p-123 resolves to /product/123.

Against a local Lander API

This is for working on Lander's own monorepo. With it running (pnpm dev), the API listens on port 8787 and a seeded test key exists:

Lander.initialize(
    this,
    apiKey = "pk_test_DemoPublishableKey000000000000",
    baseUrl = "http://10.0.2.2:8787",
    enableLogging = true,
)

The Android emulator reaches your computer at 10.0.2.2. Plain HTTP needs cleartext traffic allowed in a debug network security config (see App Links setup). Locally, link URLs on localhost, 127.0.0.1 or 10.0.2.2 resolve as acme.golander.link.

Run the example app on an emulator:

cd sdk/lander_android   # inside the Lander monorepo
./gradlew :example:installDebug

Point it at another API with ./gradlew :example:installDebug -PlanderBaseUrl=https://api.golander.link -PlanderApiKey=pk_test_....

Next steps

On this page