Testing is an integral part of modern software development. It gives you a guarantee that your code works up to specification and provides fast automated regression for refactorings and changes to the code. Tests also function as living documentation for a codebase by describing exactly how the resulting application or API is supposed to function and how different constraints may change its behavior. Perhaps most importantly, it does so in a way the developer can understand in the context of the code instead of abstract requirements that may be open to interpretation.

However, this doesn’t mean that tests have to be rigid and difficult to read. To illustrate this. Let’s look at a simple JUnit test case that uses a few common assertions

import org.junit.Assert.*
import org.junit.Test

class PersonTest {
    
  @Test
  fun testPerson() {
   	...
   	assertEquals("Alice", person.name)    
   	assertTrue(person.age > 42)
  }

}

While it’s quite straightforward for someone familiar with the framework, we can do better by taking advantage of a more natural assertions API. For example, wouldn’t it be much more natural to write something such as,

assert(person.name).isEqualTo("Alice")
assert(person.age).isGreaterThan(42)

This is where assertk comes in. assertk is a fluent assertions library inspired by AssertJ. It aims to help you write richer more expressive tests. In this article, we’ll take a look at how assertk can help you improve the readability of your test cases as well as make assertions on complex objects and collections easier.

Getting started

Setting up assertk is simple. It’s hosted on Maven Central, so you can grab it using your favorite build tool or package manager. For example, you could add it to your test configuration using gradle like so

repositories {
  mavenCentral()
}

dependencies {
  testCompile 'com.willowtreeapps.assertk:assertk:0.10'
}

At this point, you should now be able to use assertk in your tests. Let’s take a look at a couple of simple test cases to get familiar with it. Assuming we have a relatively simple data class,

data class Person(
	val name: String,
	val age: Int,
	val jobTitle: String
)

We can make assertions on its properties using the assert() function.

@Test
fun testPerson() {
	assert(person.name).isEqualTo("Alice")
	assert("age", person.age).isGreaterThan(20)
}

You can make multiple assertions on the same property by using a lambda.

@Test
fun testPerson() {
	assert(person.name).all {
   	hasLength(3)
    	isEqualTo("Bob")
	}
}

Collections Assertions

Making assertions on collections can get pretty tricky sometimes. However, assertk comes with a lot of handy functions to make this easy. Let’s take a look at some of these in action.

val fruits = listOf("orange", "pineapple", "banana", "pear")
assert(fruits).all {
	contains("banana")
	doesNotContain("apple")
	hasSize(4)
}

You can check to see if your list contains a list of elements in any order using containsAll.

assert(fruits).containsAll("orange", "pineapple")

containsExactly asserts that your list contains only the given elements in exactly the same order.

assert(fruits).containsExactly("orange", "pineapple", "banana", "pear")

Finally, containsNone asserts that your list does not contain any of the given items.

assert(fruits).containsNone("orange", "pineapple")

It’s also really easy to make assertions on specific items in your list using index.

val fruits = listOf("orange", "pineapple", "banana", "pear")
assert(fruits).index(0) { it.isEqualTo("orange") }

Maps have similarly fluent assertions.

val map = mapOf("one" to 1, "two" to 2, "three" to 3)
assert(map).all {
	contains("one" to 1)
	doesNotContain("four" to 4)
	containsExactly("one" to 1, "two" to 2, "three" to 3)
	containsAll("two" to 2)
}

Maps you can also make an assertion on a value which corresponds to a given key using the key accessor.

assert(map).key("one") {
    it.isEqualTo(1)
}

Property assertions

You can also make assertions to validate a specific property in a class using the prop function.

assert(person).prop(Person::name).isEqualTo(“Bob”)

Or you can compare multiple properties between objects using isEqualToWithGivenProperties. For example, Let’s compare a few fields between two person objects

val person = Person(name = "Matt", age = 29, email=”Software Engineer”)
val person2 = Person(name = "Matt", age = 24, email =”Software Engineer”)

assert(person).isEqualToWithGivenProperties(person2, Person::name, Person::email)

This assertion would pass because only the name properties and email properties would be compared.

Custom Assertions

To get an even higher degree of expressiveness and flexibility in your tests, you can add your own custom assertions. assertk relies heavily on extension functions as part of its public API. This makes it really easy to add your own custom assertions. Let’s add a few custom assertion to the Person class from earlier.

fun Assert<Person>.hasValidName() {
	if (actual.name.isNotBlank()) return
	expected("Name must not be blank")
}

fun Assert<Person>.hasValidEmail() {
	if (actual.email.isNotBlank()) return
	expected("Email must not be blank")
}

Assert exposes a property called actual which is the value under test. You can validate that it is correct using whatever custom logic you have available and then return if it’s correct. In the event of a failure, you should invoke the expected function with an error message to fail the test.

And that’s it! Nothing to extend or implement. Just a single function in our test which we can use just like we would any other function in the API.

assert(person).all {
	hasValidName()
	hasValidEmail()
}

Conclusion

assertk makes writing tests a lot easier with its fluent API. It’s really easy to extend to make your tests a lot more readable. For more samples and usages on all of the APIs, I recommend looking into the test sources. We also welcome your feedback and feature requests on the APIs.