Skip to content
/ nagare Public

This is an assertion/matcher library for kotlin using infix functions.

License

Notifications You must be signed in to change notification settings

cc-jhr/nagare

Repository files navigation

Build Coverage Status

nagare

Nagare (流れ) is japanese and means "flow" / "stream". This is an assertion/matcher library for kotlin using infix functions. It is easily extendable using extension functions. You can use it with both junit or testng.

Setup

In order to use this library you need a github account and a repository read token. When logged-in open the personal access tokens page. Create a new token having read:packages as the only permission.

Gradle - kotlin-dsl

Add the repository to your existing list of repositories:

repositories {
    maven {
        name = "nagare"
        url = uri("https://maven.pkg.github.com/cc-jhr/nagare")
        credentials {
            username = "your-github-username-here"            // you should probably use environment variables
            password = "your-github-packages-read-token-here" // or gradle properties here to inject the values
        }
    }
}

Add the dependency to your dependencies block:

dependencies {
    testImplementation("io.github.ccjhr:nagare:VERSION")
}

Usage

Origin of a test is the object itsef. The object must satisfy criteria

    @Test
    fun `usage test`() {
        12 mustSatisfy {
            
        }
    }

Various criteria can be described depending on the class type. Int for example provides functions like isGreaterThan/isLesserThan.

    @Test
    fun `usage test`() {
        12 mustSatisfy {
            it isGreaterThan 6
            it isBetween 10..20
        }
    }

Nullability can be checked using additional adjectives:

    @Test
    fun `usage test`() {
        val obj: String? = null
        
        obj mustSatisfy {
            it isNot Null
        }
    }

Checking a parameter of a more complex object can be achieved using a nested mustSatisfy block:

    @Test
    fun `usage test`() {
        val obj = MyObj(12, "", emptyList())

        obj mustSatisfy {
            it isNot Null
            it isOfType MyObj::class

            it.content.number mustSatisfy { number ->
                number isEqualTo 12
            }
        }
    }

    data class MyObj(
        val number: Int,
    )

Individual extensions

Lets assume we have an individual object.

data class MyObj(
    val title: String = "",
    val number: Int = 0,
)

Based on values

The extension function itself always has a similar structure. In the signature we place our object type as well as the class type that we want to test against and give the function a name. Check for nullability. It's possible to make this non-nullable, but then you would have to use !! operator or a kotlin function which uses contracts like requireNotNull if your resulting object is nullable.

inline infix fun <reified T : MyObj?> AssertionContext<T>.hasTitleMatching(regex: Regex) {
    expectNotNull(this.content)
    
    if (!regex.matches(this.content.title)) {
        fail("Expecting title <${this.content.title}> to match <${regex.pattern}>, but it doesn't.")
    }
}

Then we can use it in a test.

@Test
fun `test the newly created extension`() {
    // given
    // setting up the test case

    // when
    val result: MyObj = // code that returns an instance of MyObj

    // then
    result mustSatisfy {
        it hasTitleMatching Regex("[a-z]+-[0-9]+")
    }
}

Based on adjectives

We can create extensions using adjectives by adding an enum class for the adjectives. Here we simply imply that the object is "valid" in a way.

enum class MyObjAssertionAdjective {
    Valid
}

The only difference is that the extension function takes the enum class as parameter and we use a when to apply to the different cases.

Just for the demonstration we assume that the object is valid if it has a number greater than 0 and a title which is not blank.

inline infix fun <reified T : MyObj?> AssertionContext<T>.hasToBe(adjective: MyObjAssertionAdjective) {
    expectNotNull(this.content)
    
    when(adjective) {
        Valid -> if (content.number <= 0 || content.title.isBlank()) fail("Object is not valid.")
    }
}

Then we can use it in a test.

@Test
fun `test the newly created extension`() {
    // given
    // setting up the test case

    // when
    val result: MyObj = // code that returns an instance of MyObj

    // then
    result mustSatisfy {
        it hasToBe Valid
    }
}