17. Dezember 2021
6 Min.

Kotlin Assertion Libraries - Atrium

Looking for a neat Kotlin assertion library? We got a blog series looking at several Kotlin Assertion Libraries for you! -> Atrium

IntroductionAssertJStrikeAssert KTestes AssertionsAtrium — Kluent —

Atrium

Intro

In this blog post we will be looking at version 0.17 of Atrium. It is a relatively young library, having been started in 2018, and promises both a fluent and infix api style, as well as the option to internationalize its error messages.

Installation

Installing atrium is not as straightforward as some of the other libraries presented so far. When looking at the readme you have to notice the warning that the file you are seeing at first belongs to the unpublished master branch. You need to follow the link to the readme of the last released version. Then you need to read carefully because there are two versions of atrium you can download – providing either an infix or a fluent api.

Long story short, you need one, or both, of these:

Syntax

The fluent version of the api is not too different from AssertJ, switching out assertThat for expect:


The infix version is almost the same, with the extra weight of expect making it look like a slightly more verbose version of kotest:


You can see one of atrium’s idiosyncrasies in the above examples – list assertions that simultaneously verify that a list has elements, and that the elements satisfy a predicate. Another unusual point is that matcher lambda used for the lists is nullable, and passing a null value is a workaround for comparing list elements against null. Simply put: to make sure that no elements of your list are null you don’t do this:


but this:


Another idiosyncrasy is the way atrium handles Exceptions:


Not only does this construct look a bit strange, it prevents extracting the Exception into a variable. Due to the expect() wrapper the actually thrown Exception is not accessible, all you have is an Expect<ServiceException> intermediary, making the code read like this:


which reads much less naturally than for example Kotest’s version:


On a more positive note: while nullability requires explicit intervention it is handled in a sane manner, offering a lambda for further tests after a value has been proven to not be null:


Assertions on a single object can be grouped to make sure they are all checked, regardless of early failures:


And finally you can also build your assertions around arbitrary data extracted from your objects – if you don’t mind the extra syntax that is:


We’ll talk more about the feature and f functions when take a look at atrium’s documentation.

Error Messages

atrium’s error messages follow a simple and readable format, listing first the subject (the object being asserted on), followed by a list of failed assertions. That works great for most situations, like simple asserts:


or asserts involving list matching:


grouped asserts using features:


but can fail for objects that do not have a proper toString implementation like Arrays:


You may also have noticed that the modules described in the Installation section specify a language – en_GB – in their name. That is because atrium offers the ability to translate its assertion messages. So far only one other language is available – Swiss German. Just add ch.tutteli.atrium:atrium-translations-de_CH:0.17.0 as another dependency and your error messages turn into proper Denglisch:

Supported Types and Diversity of Assertions

The basics are covered, but outside of the usual Strings, Lists, Numbers and Maps support gets rather spotty. java.time classes are covered, but Dates are not. Optionals are covered, but outside a dedicated module adding basic support Results there is nothing more Kotlin-specific. Numeric types are supported only in as much as they are Comparable, Files and Classes have no support.

Such limits are by design, as atrium’s philosophy is to only implement new features based on popular demand, as its readme states:

According to the YAGNI principle this library does not yet offer a lot of out-of-the-box assertion functions. More functions will follow but only if they are used somewhere by someone. So, let us know if you miss something by creating a feature request.

Custom Asserts

Custom assertions in Atrium are written as extension functions for the Expect<T> type. Your extensions must use something  called "_logic" to createAndAppend the assertion you are writing:


It is also possible to re-use other assertions and group them:


A fluent composition is also possible when using the fluent api:


So while the syntax is mostly simple and flexible it also looks like the use of the strange _logic value is some sort of exposed implementation detail or leaking abstraction. We’ll take a look at, and judge, _logic's official explanation in the next chapter.

Quality of Documentation

Documentation is one of the weaker points of Atrium. Not for lack of trying – every function has a kdoc description and the readme on github is thousands of lines long – but rather because it seems to be written for the wrong target group. Kdoc strings oftentimes read like technical descriptions meant for Atrium’s developers, instead of its users. The github readme (the only source of documentation outside the IDE) likewise often hides useful information in between what feels like paragraphs of digressions.

Let’s look at some concrete examples – the feature and f functions that we mentioned when talking about syntax. What is a feature? Looking at just its signature we can glean that this function operates on a pretty high level of abstraction:


So let’s try the kdoc next:

Extracts a feature out of the current subject of this expectation, based on the given provider, creates a new Expect for it, applies an assertion group based on the given assertionCreator for the feature and returns the initial Expect with the current subject.

That’s a lot of explanations, most of which you will not understand unless you already speak the language of atrium. Let’s try f next:

Creates a MetaFeature for the given property => use p in case of ambiguity issues. Notice for expectation function writers: you should use feature and pass a class reference instead of using this convenience function (e.g. feature(List::size)). This way we are always able to report the property, even if the subject is not defined which occurs if a previous transformation of the subject could not be carried out.

What about _logic? It is required to build new assertions, so its documentation should tell us what it is and how it can be used.

Entry point to the logic level of Atrium — which is one level deeper than the API — on which assertion functions do not return Expects but Assertions and the like.
Use _logicAppend in case you want to create and append an Assertion to this Expect.

These explanations sound like they start in medias res, as if the exposition has already been made, and now we need to clear up some technical details. And while these kind of deep details do have their place, there’s precious little in this documentation that would guide us towards being able to write – and understand – the syntax of feature({ f(it::name) }).

These problems are of course much less severe for the overwhelming majority of assertions you will see in everyday use. These are, much like the other libraries we looked at, largely self-evident and do not really require any special documentation outside a first Hello World example. Unfortunately another flaw is a lack of a list of all possible assertions. The best you can do is a link to auto-generated javadoc, listing every single function and type defined by atrium.

Active Development

Atrium is under active development, commits are made, issues are answered and PRs are merged regularly. However it is maintained largely by a single developer, putting its long-term future into question.

IntroductionAssertJStriktAssertKKoTest AssertionsAtrium — Kluent —

Artikel kommentieren