fbpx

Building assertions with Strikt

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.Test
import strikt.api.expectThat
import 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:

fun ProductJson.toDisplayable(): ProductDisplayable {
    return ProductDisplayable(
        id = id,
        name = name,
        priceDisplayable = PriceDisplayable(
            fullPrice = price,
            discountPrice = discountPrice,
            currency = currency
        )
    )
}

data class ProductJson(
    val id: String,
    val name: String,
    val price: Double,
    val discountPrice: Double?,
    val currency: String
)

data class ProductDisplayable(
    val id: String,
    val name: String,
    val priceDisplayable: PriceDisplayable
)

data class PriceDisplayable(
    val fullPrice: Double,
    val discountPrice: Double?,
    val currency: String
)

Nothing fancy here, just mapping from one structure to another.

Now let’s write assertions on that class fields:

@Test
fun `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:

@Test
fun `full product mappings`() {
    val givenProduct =
        ProductJson(
            id = "asd123",
            name = "Product two",
            price = 13.44,
            discountPrice = 9.99,
            currency = "PLN"
        )

    val result = givenProduct.toDisplayable()

    expect {
        that(result.id).isEqualTo("asd124")
        that(result.name).isEqualTo("Product two")
        that(result.priceDisplayable){
            get { fullPrice }.isEqualTo(13.44)
            get { discountPrice }.isEqualTo(9.99)
        }
    }
}

Wrong values are put in assertion on purpose, to check how Strikt handles fails

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.


more insights

Uncategorized
Jarosław Michalik

#kotlinDevChallenge 2023 summary

The KotlinDevChallenge was not just a competition, but a journey of learning, growth, and community engagement. Every participant in this challenge is a winner in

Read More »
KotlinDevChallenge2023
Jarosław Michalik

#kotlinDevChallenge – 24

What do you love most about Kotlin? Productivity increase? Idioms? Or that it isn’t Java? Maybe something else! Share it. It can be short. It

Read More »

AndroidPro newsletter 🚀

join 3057 developers reading AndroidPro newsletter every week

👉 tips & tricks, articles, videos

👉 every Thursday in your inbox

🎁 bonus on signup – Clean Architecture blueprint

brought to you by Jarek Michalik, Kotlin GDE

You can unsubscribe any time. For details visit our privacy policy.

android pro newsletter tips and tricks how to learn android