Traversing Assertion Subjects

Although you can obviously write assertions for the properties of an object with code like this:

expectThat(map.size).isEqualTo(1)
expectThat(list.first()).isEqualTo("fnord")
expectThat(person.name).isEqualTo("Ziggy")

Sometimes it's useful to be able to transform an assertion on a subject to an assertion on a property of that subject, or the result of a method call. Particularly when using soft assertion blocks.

Strikt allows for this using the Assertion.Builder<T>.get method.

Using get with property or method references

The first override of get takes a property or (zero argument) method reference as a parameter. The get method returns an Assertion.Builder<R> where the new subject (whose type is R) is the value returned by invoking that property or method on the current subject.

This is useful for making assertions about the properties of an object, or the values returned by methods, particularly if you want to use a block-style assertion to validate multiple object properties.

val subject = Person(name = "David", birthDate = LocalDate.of(1947, 1, 8))
expectThat(subject) {
  get(Person::name).isEqualTo("David")
  get(Person::birthDate).get(LocalDate::getYear).isEqualTo(1947)
}

Using get with lambdas

An alternate version of the get method takes a lambda whose receiver is the current subject.

val subject = Person(name = "David", birthDate = LocalDate.of(1947, 1, 8))
expectThat(subject) {
  get { name }.isEqualTo("Ziggy")
  get { birthDate.year }.isEqualTo(1971)
}

Strikt will attempt to read the test source to find out the name of the variables. This example produces output that looks like this:

▼ Expect that Person(name=David, birthDate=1947-01-08):
  ▼ name:
    ✗ is equal to "Ziggy"
            found "David"
  ▼ birthDate.year:
    ✗ is equal to 1971
            found 1947

Performance considerations

Reading the test source can be costly performance-wise. If you are running large-scale parallel tests, property-based testing, or something similar, it probably makes sense to avoid this penalty. You can do so by:

  • providing an explicit description parameter to get in addition to the lambda.
  • using get with a property/method reference rather than a lambda.

In either of those cases Strikt will not derive a description by attempting to read the source.

Mapping elements of collections

If the assertion subject is an Iterable Strikt provides a map function much like the one in the Kotlin standard library. It is effectively like using get on each element of the Iterable subject.

val subject: List<Person> = getPersonList()
expectThat(subject)
  .map(Person::name)
  .containsExactly("David", "Ziggy", "Aladdin", "Jareth")

In this case the map function is transforming the Assertion.Buidler<List<Person>> into an Assertion.Builder<List<String>> by applying the name property to each element.

Re-usable mapping extensions

If you find yourself frequently using get for the same properties or methods, consider defining extension property or method to make things even easier.

For example:

val Assertion.Builder<Person>.name: Assertion.Builder<String>
  get() = get(Person::name)

val Assertion.Builder<Person>.yearOfBirth: Assertion.Builder<Int>
  get() = get("year of birth") { birthDate.year }

You can then write the earlier example as:

val subject = Person(name = "David", birthDate = LocalDate.of(1947, 1, 8))
expectThat(subject) {
  name.isEqualTo("David")
  yearOfBirth.isEqualTo(1947)
}

Built-in traversals

Strikt has a number of built in traversal properties and functions such as Assertion.Builder<List<E>>.first() which returns an Assertion.Builder<E> whose subject is the first element of the list. See the API docs for details.