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().
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:
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.
- 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.
assertAbout(compressWhitespaces()).that("string string").isEqualTo("string string")
- 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.
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.
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
StringSubjectwith checks that compresses multiple whitespaces into one similarignoreCase().From Java's point of view
compressWhitespaces()is like a static method that hasStringSubjectas argument.The first problem I ran into is that
actual()isprotected finalinSubject. There wasn't a way for me to get the actual value from theStringSubjectthat I'm "extending". (My solution is to use a reflective call for now)Similar to
StringSubject.CaseInsensitiveStringComparisonI planned to implement the checks in a class, that doesn't extendSubject. This way I can add methods likeisEqualTo()when I actually implement them. There were multiple problems with this approach:CaseInsensitiveStringComparisonis an inner class, it gets access toactual()and other methods throughStringSubject. 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 thatSubjecthas 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.Subject. I could hide the implementation behind an interface, but this wouldn't allow providing a factory method that creates theSubject.Factory, what is necessary forassertAbout(..)and quite useful in Java.Subject, meaning every caller sees all the methods, that I might not have implemented. In fact, my class implementsisEqualTo()right now, but notisNotEqualTo(). 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.To summarize those are the things I wish would be available or different.
actual()value from outside.Subject.