kotlin-test: How to test for a specific type like: "is y instance of X"
Asked Answered
K

4

24

How to test if a val/var is of an expected type?

Is there something I am missing in Kotlin Test, like:

value shouldBe instanceOf<ExpectedType>()

Here is how I implemented it:

inline fun <reified T> instanceOf(): Matcher<Any> {
    return object : Matcher<Any> {
        override fun test(value: Any) =
                Result(value is T, "Expected an instance of type: ${T::class} \n Got: ${value::class}", "")

    }
}
Keratose answered 29/4, 2019 at 11:52 Comment(3)
must it be done with a custom matcher? Could just assertTrue { value is ExpectedType }Jaquelynjaquenetta
My goal is not to reinvent the wheel. If there is some 1 liner in kotlin-test, I would prefer it in favour of a custom implmentation.Keratose
assertTrue { value is ExpectedType } would work, but it doesn't read fluid and the exception message doesn't reveal any information of the actual exception, in case of an failure.Keratose
N
42

In KotlinTest, a lot is about proper spacing :) You can use should to get access to a variety of built-in matchers.

import io.kotlintest.matchers.beInstanceOf
import io.kotlintest.should

value should beInstanceOf<Type>()

There is also an alternative syntax:

value.shouldBeInstanceOf<Type>()

See here for more information.

Niko answered 29/4, 2019 at 12:1 Comment(5)
That spacing is weired. I wonder why they added the alias instanceOf(expected: KClass<*>): Matcher<Any?> but not an refied infix version like in the question.Keratose
shouldBe is comparing against equality of its left and right argument. While they could have overloaded it for matchers, it would be confusing (e.g. what if the right-hand argument is passed as type Any?). I think the circumstance that the same function name always performs the same validation helps understanding, even if less intuitive from an English language point of view.Niko
FYI shouldBe is implemented to be usable with Matcher 's, so it is not tied to equality checks. Here you can see its implementation: dsl.ktKeratose
Thanks! Good to know, even though questionable that x should y, x shouldBe y and x shouldHave y can be used interchangeably with matchers. Apparently they want to make everyone happy...Niko
The idea behind shouldBe and shouldHave was to allow matchers to be named things like "size" so you could then write, a shouldHave size(4). In hindsight it was not a great idea.Sweetscented
M
4

Since Kotlin 1.5 there is a nice solution included in kotlin-test:

assertIs<TypeExpected>(value)

This will not only assert that value is of type TypeExpected, it will also smart cast value, so that you can access all methods of TypeExpected. Just include the dependency, e.g:

testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.7.10")

And you can do stuff like this

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs

class AssertIsTests {

    @Test
    fun `test for list`() {
        val list = Collector.List.collect(1, 1, 2, 2, 3)
        assertEquals(5, list.size)

        // assertEquals(1, list[0])  won't compile: list is of type Collection<Int> which doesn't support []
        assertIs<List<Int>>(list)         // from now on list is of type List<Int>!
        assertEquals(4, list.indexOf(3))  // now there are all list methods
        assertEquals(1, list[0])          // including indexed getters
    }

    @Test
    fun `test for set`() {
        val set = Collector.Set.collect(1, 1, 2, 2, 3)
        assertEquals(3, set.size)  // Set with duplicates removed

        assertIs<Set<Int>>(set)    // from now on set is of Type Set<Int>
    }
}

enum class Collector {
    List {
        override fun collect(vararg args: Int) = args.toList()
    },
    Set {
        override fun collect(vararg args: Int) = args.toSet()
    };

    abstract fun collect(vararg args: Int): Collection<Int>
}
Merrythought answered 19/8, 2022 at 12:11 Comment(0)
C
0

In 2020 Kotlintest library was renamed to Kotest and package names changed accordingly. So I decided to publish an updated version of the @TheOperator's answer.

To check that a value is an instance or a subtype of Type:

import io.kotest.matchers.should
import io.kotest.matchers.types.beInstanceOf

value should beInstanceOf<Type>()

An alternative to this is a method from Core Matchers:

import io.kotest.matchers.types.shouldBeInstanceOf

val castedValue: Type = value.shouldBeInstanceOf<Type>()

Note that in this case a value casted to the specified type is returned, which can be used in further validations.


In case you need exact type matching, use another set of methods:

import io.kotest.matchers.should
import io.kotest.matchers.types.beOfType
import io.kotest.matchers.types.shouldBeTypeOf

value should beOfType<Type>()
val castedValue: Type = value.shouldBeTypeOf<Type>()
Charinile answered 4/5, 2023 at 11:4 Comment(0)
H
0

You can write assert statement using JUnit like

assertEquals(ExpectedClassName::class.java, actualInstance.javaClass)
Hafiz answered 4/11, 2023 at 15:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.