google / google/truth

Provide support for Kotlin extension methods on Subjects

Open
#536 10 comments 7 reactions 0 assignees View on GitHub
P3
Dominant language
Java
Stars
2.8k
Forks
275
Avg merge
7m
Merged PRs (30d)
4

Description

The title might be a little off. We recently converted the Point of Sale Android codebase at Square from AssertJ to Truth and used Kotlin for our custom Subjects. I try to walk you through the issues I ran into, maybe it is beneficial for your future API considerations.

Kotlin supports extension function, what allows "extending" existing Subjects easily and in a fluent way. My goal was to extend the `StringSubject` with checks that compresses multiple whitespaces into one similar `ignoreCase()`.
```kotlin
assertThat("hello world").compressWhitespaces().isEqualTo("hello world")
```
From Java's point of view `compressWhitespaces()` is like a static method that has `StringSubject` as argument.

The first problem I ran into is that `actual()` is `protected final` in `Subject`. There wasn't a way for me to get the actual value from the `StringSubject` that I'm "extending". (My solution is to use a reflective call for now)

Similar to `StringSubject.CaseInsensitiveStringComparison` I planned to implement the checks in a class, that doesn't extend `Subject`. This way I can add methods like `isEqualTo()` when I actually implement them. There were multiple problems with this approach:

1. `CaseInsensitiveStringComparison` is an inner class, it gets access to `actual()` and other methods through `StringSubject`. My class doesn't have access to these methods. My biggest problem was that I couldn't construct failure messages easily. I have the impression that `Subject` has too many concerns: It provides the API for checks, but also gives you access to methods to create error messages. It would be helpful, if both things were separated.
2. So my class had to extend `Subject`. I could hide the implementation behind an interface, but this wouldn't allow providing a factory method that creates the `Subject.Factory`, what is necessary for `assertAbout(..)` and quite useful in Java.
```kotlin
assertAbout(compressWhitespaces()).that("string string").isEqualTo("string string")
```
3. I had to expose that my class extends `Subject`, meaning every caller sees all the methods, that I might not have implemented. In fact, my class implements `isEqualTo()` right now, but not `isNotEqualTo()`. You could treat it like a not finished implementation for the API and I could throw an exception / error when calling this method, but from an API point of view the caller doesn't know that. They shouldn't see the method at all. This leads to false positives at the moment.
```kotlin
assertAbout(compressWhitespaces()).that("string string").isNotEqualTo("string string") // doesn't throw at the moment
```

To summarize those are the things I wish would be available or different.
* Get access to the `actual()` value from outside.
* Make creating error messages possible without extending `Subject`.
* Split the default check methods into separate classes or interfaces and provide default implementations in a composable way.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.