Strikt is assertion library built for Kotlin with API that allows building fluent assetions.
To add library to project, add following dependency to your build.gradle
repositories { mavenCentral()}dependencies { testImplementation("io.strikt:strikt-core:0.31.0")}
To get latest version, visit Github Releases page: https://github.com/robfletcher/strikt/releases
Basic usage
Good assertion library should give us readable assertion errors. Strikt renders error in the following style:
import org.junit.Testimport strikt.api.expectThatimport strikt.assertions.*class Test { @Test fun `just checking strikt`() { expectThat("non-empty-string") .isNotEmpty() .hasLength(12) }}
Running this test prints the following:
▼ Expect that "non-empty-string": ✓ is not empty ✗ has length 12 found 16
It is clear which assertion passed and which failed.
More complex assertions
Lets consider more complex example for multi assertion – mapping product entity which may be for some reason discounted:
Now let’s write assertions on that class fields:
@Testfun `full product mappings`() { val givenProduct = ProductJson( id = "asd123", name = "Product two", price = 13.44, discountPrice = 9.99, currency = "PLN" ) val result = givenProduct.toDisplayable() expectThat(result) .describedAs("given product with discount") .and { get { id }.isEqualTo("asd123") } .and { get { name }.isEqualTo("Product two") } .and { get { priceDisplayable.fullPrice } .describedAs("Full product price") .isEqualTo(13.44) } .and { get { priceDisplayable.discountPrice } .describedAs("Discounted product price") .isEqualTo(9.99) }}
We can build and chain multiple assertions and still have readable output. Please keep in mind, that with this notation the first assertion fail will terminate the test.
Handling exceptions
Strikt gives us also API for testing exceptions, consider system under test which will throw UnsupportedOperationException when we try to add items with quantity=0:
class ProductsService { fun add(productId: String, count: Int) { if (count < 1) { throw UnsupportedOperationException("Cannot add 0 items to basket") } else { //do logic } }}class Test { @Test fun `it should throw exception`() { val productsService = ProductsService() expectThrows<UnsupportedOperationException> { productsService.add("asd123", 0) }.and { get { message }.isEqualTo("Cannot add 0 items to basket") } }}
We can specify type of exception that should be thrown, we put system under test call in function body, and then we have possibility to additionally assert on exception.
See also
Soft assertions
Strikt supports also soft assertions – meaning that all assertions in block will be executed and we get the full report. Consider previous example with mapping discounted product:
Which will result in error:
▼ Expect that "asd123": ✗ is equal to "asd124" found "asd123"▼ Expect that "Product two": ✓ is equal to "Product two"▼ Expect that PriceDisplayable(fullPrice=13.44, discountPrice=9.99, currency=PLN): ▼ fullPrice: ✓ is equal to 13.44 ▼ discountPrice: ✓ is equal to 9.99
See also:
Summary
Strikt is not hard to use, gives nice assertion API and gives beautiful assertion error stacktraces. I appreciate using ✗ and ✓ characters – they increase readability significantly.
There are a lot of assertion frameworks on the market. Using assertion methods built into framework you already may be enough.It’s worth to take a look at Strikt and consider putting it as test dependency in your project.