Adding custom Truth subjects

Custom truth subjects

Every subject class should extend Subject and follow the naming schema [ClassUnderTest]Subject. The Subject must also have a constructor that accepts FailureMetadata and a reference to the object under test, which are both passed to the superclass.

class NavControllerSubject private constructor(
    metadata: FailureMetadata,
    private val actual: NavController
) : Subject(metadata, actual) { }

Subject factory

The Subject class should also contain two static fields; a Subject Factory and anassertThat() shortcut method.

A subject Factory provides most of the functionality of the Subject by allowing users to perform all operations provided in the Subject class by passing this factory to about() methods. E.g:

assertAbout(navControllers()).that(navController).isGraph(x) where isGraph() is a method defined in the Subject class.

The assertThat() shortcut method simply provides a shorthand method for making assertions about the Subject without needing a reference to the subject factory. i.e., rather than using assertAbout(navControllers()).that(navController).isGraph(x) users can simply useassertThat(navController).isGraph(x).

companion object {
    fun navControllers(): Factory<NavControllerSubject, NavController> =
        Factory<NavControllerSubject, NavController> { metadata, actual ->
            NavControllerSubject(metadata, actual)
        }

    @JvmStatic
    fun assertThat(actual: NavController): NavControllerSubject {
        return assertAbout(navControllers()).that(actual)
    }
}

Assertion methods

When creating assertion methods for your custom Subject the names of these methods should follow the Truth naming convention.

To create assertion methods it is necessary to either delegate to an existing assertion method by using Subject.check() or to provide your own failure strategy by usingfailWithActual() or failWithoutActual().

When using failWithActual() the error message will display thetoString() value of the Subject object. Additional information can be added to these error messages by using fact(key, value) or simpleFact(value) where fact() will be output as a colon separated key, value pair.

fun isGraph(@IdRes navGraph: Int) {
    check("graph()").that(actual.graph.id).isEqualTo(navGraph)
}

// Sample Failure Message
value of          : navController.graph()
expected          : 29340
but was           : 10394
navController was : {actual.toString() value}
fun isGraph(@IdRes navGraph: Int) {
    val actualGraph = actual.graph.id
    if (actualGraph != navGraph) {
        failWithoutActual(
            fact("expected id", navGraph.toString()),
            fact("but was", actualGraph.toString()),
            fact("current graph is", actual.graph)
        )
    }
}

// Sample Failure Message
expected id      : 29340
but was          : 10394
current graph is : {actual.graph.toString() value}

Testing

When testing expected successful assertions it is enough to simply call the assertion with verified successful actual and expected values.

private lateinit var navController: NavController
@Before
fun setUp() {
    navController = NavController(
        ApplicationProvider.getApplicationContext()
    ).apply {
        navigationProvider += TestNavigator()
        // R.navigation.testGraph == R.id.test_graph
        setGraph(R.navigation.test_graph)
    }
}

@Test
fun testGraph() {
    assertThat(navController).isGraph(R.id.test_graph)
}

To test that expected failure cases you should use the assertThrows function from the AndroidX testutils module. The assertions.kt file contains two assertThrows functions. The second method, which specifically returns a TruthFailureSubject, should be used since it allows for validating additional information about the FailureSubject, particularly that it contains specific fact messages.

@Test
fun testGraphFailure() {
    with(assertThrows {
        assertThat(navController).isGraph(R.id.second_test_graph)
    }) {
        factValue("expected id").isEqualTo(R.id.second_test_graph.toString())
        factValue("but was").isEqualTo(navController.graph.id.toString())
        factValue("current graph is").isEqualTo(navController.graph.toString())
    }
}

Helpful resources

Truth extension points