Coder Social home page Coder Social logo

assertk's Introduction

assertk

CircleCI Maven Central Sonatype Snapshot

assertk is a fluent assertion library for Kotlin inspired by AssertJ.

Why another assertion library?

You might be asking, "If AssertJ already exists, why create another library?". It's true, assertk is very similar to AssertJ. But assertk is written in Kotlin so it has one major advantage: extension methods. This makes adding your own assertion methods far simpler.

See Custom Assertions below to find out how to do this.

Setup

repositories {
    mavenCentral()
}

dependencies {
    testImplementation("com.willowtreeapps.assertk:assertk:0.28.0")
}

assertk has full multiplatform support and it can be used in jvm, js, or native projects. For example to set it up using the multiplatform plugin:

plugins {
    kotlin("multiplatform")
}

kotlin {
    sourceSets {
        val commonTest by getting {
            dependencies {
                implementation("com.willowtreeapps.assertk:assertk:0.28.0")
            }
        }
    }
}

Usage

Simple usage is to wrap the value or property you are testing in assertThat() and call assertion methods on the result.

import assertk.assertThat
import assertk.assertions.*

class PersonTest {
    val person = Person(name = "Bob", age = 18)

    @Test
    fun testName() {
        assertThat(person.name).isEqualTo("Alice")
        // -> expected:<["Alice"]> but was:<["Bob"]>
    }

    @Test
    fun testAge() {
        assertThat(person.age, "age").isGreaterThan(20)
        // -> expected [age] to be greater than:<20> but was:<18>
    }

    @Test
    fun testNameProperty() {
        assertThat(person::name).isEqualTo("Alice")
        // -> expected [name]:<["Alice"]> but was:<["Bob"]>
    }
}

You can see all built-in assertions in the docs.

Nullability

Since null is a first-class concept in kotlin's type system, you need to be explicit in your assertions.

val nullString: String? = null
assertThat(nullString).hasLength(4)

will not compile, since hasLength() only makes sense on non-null values. You can chain isNotNull() to handle this.

val nullString: String? = null
assertThat(nullString).isNotNull().hasLength(4)
// -> expected to not be null

This will first ensure the string is not null before running any other checks.

Multiple assertions

You can assert multiple things on a single value by providing a lambda as the second argument. All assertions will be run even if the first one fails.

val string = "Test"
assertThat(string).all {
    startsWith("L")
    hasLength(3)
}
// -> The following 2 assertions failed:
//    - expected to start with:<"L"> but was:<"Test">
//    - expected to have length:<3> but was:<"Test"> (4)

You can wrap multiple assertions in an assertAll to ensure all of them get run, not just the first one.

assertAll {
    assertThat(false).isTrue()
    assertThat(true).isFalse()
}
// -> The following 2 assertions failed:
//    - expected to be true
//    - expected to be false

Iterable/List Assertions

You can assert on the contents of an Iterable/List with the various contains* functions. They have different semantics as follows:

Assertion Description
containsAtLeast Asserts the iterable contains at least the expected elements, in any order. The collection may also contain additional elements.
containsSubList Asserts that a collection contains a subset of items the same order, but may have additional elements in the list.
containsOnly Asserts the iterable contains only the expected elements, in any order. Duplicate values in the expected and actual are ignored.
containsExactlyInAnyOrder Asserts the iterable contains exactly the expected elements, in any order. Each value in expected must correspond to a matching value in actual, and visa-versa.
containsExactly Asserts the list contains exactly the expected elements. They must be in the same order and there must not be any extra elements.
containsNone Asserts the iterable does not contain any of the expected elements

Extracting data

There's a few ways you extract the data you want to assert on. While you can do this yourself before calling the assertion, these methods will add the extra context to the failure message which can be helpful.

The simplest way is with prop(). It will take a property (or function, or a name and a lambda) and return an assertion on that property.

val person = Person(age = 22)
assertThat(person).prop(Person::age).isEqualTo(20)

// -> expected [age]:<2[0]> but was:<2[2]> (Person(age=22))

For collections, you can use index() to pull a specific index from a list, and key() to pull a specific value from a map.

assertThat(listOf(1, 2, 3)).index(1).isEqualTo(1)

// -> expected: [[1]]:<1> but was:<2> ([1, 2, 3])

assertThat(mapOf("one" to 1, "two" to 2, "three" to 3)).key("two").isEqualTo(1)

// -> expected: [["two"]]:<1> but was:<2> ({"one"=1, "two"=2, "three"=3})

You can also extract a property from a collection using extracting().

val people = listOf(Person(name = "Sue"), Person(name = "Bob"))
assertThat(people)
    .extracting(Person::name)
    .containsExactly("Sue", "Bob")

Exceptions

If you expect an exception to be thrown, you can use assertFailure which takes a lambda.

assertFailure {
    throw Exception("error")
}.hasMessage("wrong")
// -> expected [message] to be:<["wrong"]> but was:<["error"]>

Table Assertions

If you have multiple sets of values you want to test with, you can create a table assertion.

tableOf("a", "b", "result")
    .row(0, 0, 1)
    .row(1, 2, 4)
    .forAll { a, b, result ->
        assertThat(a + b).isEqualTo(result)
    }
// -> the following 2 assertions failed:
//    on row:(a=<0>,b=<0>,result=<1>)
//    - expected:<[1]> but was:<[0]>
//    on row:(a=<1>,b=<2>,result=<4>)
//    - expected:<[4]> but was:<[3]>

Up to 4 columns are supported.

Custom Assertions

One of the goals of this library is to make custom assertions easy to make. All assertions are just extension methods.

fun Assert<Person>.hasAge(expected: Int) {
    prop(Person::age).isEqualTo(expected)
}

assertThat(person).hasAge(20)
// -> expected [age]:<2[0]> but was:<2[2]> (Person(age=22))

For completely custom assertions, you have a few building blocks. given will give you the actual value to assert on, and expected() and show() will help you format your failure message.

fun Assert<Person>.hasAge(expected: Int) = given { actual ->
    if (actual.age == expected) return
    expected("age:${show(expected)} but was age:${show(actual.age)}")
}

assertThat(person).hasAge(20)
// -> expected age:<20> but was age:<22>

You can also build assertions that chain by using transform. This allows you to both assert on the actual value, and return something more specific that additional assertions can be chained on.

fun Assert<Person>.hasMiddleName(): Assert<String> = transform(appendName("middleName", separator = ".")) { actual ->
    if (actual.middleName != null) {
        actual.middleName
    } else {
        expected("to not be null")
    }
}

assertThat(person).hasMiddleName().isEqualTo("Lorie")

// -> expected [middleName]:to not be null

Note: this is a bit of a contrived example as you'd probably want to build this out of existing assertions instead.

fun Assert<Person>.hasMiddleName(): Assert<String> = prop(Person::middleName).isNotNull()

The general rule of thumb is to prefer building out of the existing assertions unless you can give a more meaningful error message.

Additional Tools

  • Assertk Lint - A set of lint rules to encourage proper use of assertk.

Contributing to assertk

Contributions are more than welcome! Please see the Contributing Guidelines and be mindful of our Code of Conduct.

assertk's People

Contributors

evant avatar rf43 avatar jakewharton avatar nishtahir avatar linkedlist avatar andriesfc avatar derektom14 avatar dineshba avatar dattish avatar dibog avatar afeozzz avatar ansman avatar mkobit avatar dzirbel avatar yogurtearl avatar madhumithap avatar zomzog avatar ilikeyourhat avatar jarlehansen avatar eygraber avatar carltonwhitehead avatar alllex avatar pt2121 avatar pradeepblue avatar mrnustik avatar mr-thierry avatar adyang avatar rock3r avatar sebastianaigner avatar ryanbrookepayne avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.