Matt's Tidbits #64 - Digging into some Ham...crest assertions
Tags: mattstidbits, android, kotlin, testing
Published: 2020-04-14 13:32:13

[Last week I shared a quick tidbit about Android Gradle Plugin 3.6.2.](https://medium.com/@matthew.b.groves/matts-tidbits-63-some-good-news-about-agp-3-6-2-b2fc60ae803c) This time, I'd like to share an interesting error with Hamcrest assertions and how I resolved it!

I was writing some new code (and some unit tests!) this week, and stumbled on a strange error when running my unit tests:
```
None of the following functions can be called with the arguments supplied:

public open fun <T : Any!> assertThat(p0: (???..???), p1: Matcher<in (???..???)>!): Unit defined in org.hamcrest.MatcherAssert

public open fun assertThat(p0: String!, p1: Boolean): Unit defined in org.hamcrest.MatcherAssert
```

The code in question looked like this:
{% gist https://gist.github.com/mpeng3/db77a5709c928a22dadf057f2f337c2d %}

This seemed a little puzzling to me - I had used this pattern a thousand times before - why was it failing now?

It turns out, Hamcrest's `is` matcher requires that the types of both arguments be exactly the same. Another way of saying this is that the `is` matcher is invariant. What I was expecting is that it would be [covariant](https://kotlinlang.org/docs/reference/generics.html#variance).

Here's the definition of the `is` matcher:
```java
public static <T> org.hamcrest.Matcher<T> is(T value) {
  return org.hamcrest.core.Is.<T>is(value);
}
```

To get the behavior I want, the definition should be:
```java
public static <T> org.hamcrest.Matcher<? extends T> is(T value) {
  return org.hamcrest.core.Is.<T>is(value);
}
```

In Kotlin, this would use `Matcher<out T>` instead.

Since I can't change Hamcrest, what are our options?
1. Add a cast to the `is` method:
`assertThat(mapContainer.getMap(), is<Map<String, Int>>(map))`
**Pros:** Quick
**Cons:** Doing this results in Android Studio showing this as a lint warning, and offers you the quick fix to "Remove explicit type arguments", which if you do, will no longer compile. You could add an annotation to ignore the lint warning, but I think that will start to get messy very quickly.

2. Explicitly cast the map to its parent type:
`assertThat(mapContainer.getMap(), is(map as Map<String, Int>))`
**Pros:** No lint warning!
**Cons:** Cumbersome to have to type this out every time.

There are some other options that involve  bad practices, so I'm listing them separately here:
1. Do **NOT** cast the left side to be the same type as the right side
`assertThat(mapContainer.getMap() as LinkedHashMap<String, Int>, is(map))`
**Cons:** You're making assumptions that may not be true!

2. Do **NOT** swap the arguments (putting the expected type on the left and the actual value on the right)
`assertThat(map, is(mapContainer.getMap()))`
**Cons:** This makes the error messages you receive from Hamcrest confusing and incorrect.

Do we have any other options?

The answer is… Truth!
https://truth.dev

Truth supports similar capabilities to Hamcrest, but uses chained method calls so the IDE can suggest assertions that apply to the type of data you are working with! Hamcrest often feels frustrating in that you have to just know what the various matchers are.

Here's an example of our check using Truth:
```kotlin
assertThat(mapContainer.getMap()).isEqualTo(map)
```

This compiles and runs just fine! 🎉

If we look at the code for `isEqualTo()`, we see that it will accept any type of object on the right-hand side, even things that wouldn't make sense, like the number `5`, because the input type is Object (`Any?` in Kotlin):
`public final void isEqualTo(Object other) {...}`

Truth was created by Google and is very similar to the popular [AssertJ library](https://assertj.github.io/doc/). However, it has a simpler API and all of Truth's features work out-of-the-box with Android (and it's even being bundled as part of the AndroidX Test library, with helpful assertions for some Android types, such as Bundles, Intents, etc.)

Truth has a helpful article if you're curious in learning more about it and how it compares to Hamcrest and AssertJ: https://truth.dev/comparison

I hope you learned something new today, and will look into Truth for writing assertions in your code! Let me know how it goes in the comments - I would be interested to hear how your experience compares with my own. And, please follow me on Medium if you're interested in being notified of future tidbits.

Interested in joining the awesome team here at Intrepid? [We're hiring!](https://intrepid.io/careers)

*This tidbit was originally delivered on April 10, 2020.*