Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support assertThrows() with suspending functions in Kotlin #1851

Closed
DerkSchooltink opened this issue Apr 8, 2019 · 10 comments
Closed

Support assertThrows() with suspending functions in Kotlin #1851

DerkSchooltink opened this issue Apr 8, 2019 · 10 comments

Comments

@DerkSchooltink
Copy link

DerkSchooltink commented Apr 8, 2019

Often when I write a unit test that has to assert that a certain exception is thrown I use the assertThrows() method. But I find myself regularly having to test suspending functions, so I have to wrap my function with a runBlocking(). This is a lot of repeating code if your entire codebase is heavily based on coroutines.

Proposed fix

I couldn't possibly have made this issue without also thinking about a solution.

Effectively, this is what I came up with:

private inline fun <reified T : Throwable> assertThrows(
        noinline executable: suspend () -> Unit
    ) = assertThrows(T::class.java) {
        runBlocking {
            executable()
        }
    }

This way you can pass suspending functions as parameter executable, which will be invoked in a runBlocking() to make sure the suspending function joins before continuing.

tl;dr

I think it would be a good addition to JUnit Jupiter to have functions that allow easy testing of suspending functions.

@marcphilipp
Copy link
Member

Thanks for creating the issue! While I'm not familiar with Kotlin's coroutines and suspending functions, but I can see how that would make sense.

@DerkSchooltink Would you be interested in submitting a PR?

@marcphilipp marcphilipp added this to the 5.x Backlog milestone Apr 8, 2019
@DerkSchooltink DerkSchooltink reopened this Apr 9, 2019
@sbrannen sbrannen changed the title Support thrown exception assertions on suspending functions Support assertThrows() with suspending functions in Kotlin Apr 9, 2019
@DerkSchooltink
Copy link
Author

DerkSchooltink commented Apr 9, 2019

I have created a PR with my suggested fix, but I'm having some issues with Azure pipeline not compiling coroutines library from Kotlin too well. I'm seeing actual bytecode in the stacktrace. Might be an issue on my end, will investigate later.

#1853

@mkobit
Copy link
Contributor

mkobit commented Apr 9, 2019

Probably related to https://youtrack.jetbrains.com/issue/KT-22228

@marcphilipp
Copy link
Member

Reopening due to #2042 (comment)

@marcphilipp marcphilipp reopened this Apr 1, 2020
@marcphilipp
Copy link
Member

Resolves in master in c5d658c.

@binkley
Copy link

binkley commented Jun 6, 2020

@DerkSchooltink

Sorry to comment on a long-closed issue. I tried the "simplest thing". If the unit test function is not itself suspending, how does it test a suspending function?

internal class ScratchTest {
    @Test
    fun `should test suspending functions`() {
        assertThrows<IllegalStateException> { failing() }
    }
}

private suspend fun failing() {
    delay(1L) // pretend
    error("Fail on purpose")
}

The compiler rightfully complains that "failing()" cannot be called from "assertThrows" as there is no suspension context. What is the scope and context for test functions? (https://kotlinlang.org/docs/reference/coroutines/basics.html)

The merged commit did not include tests I could examine as examples.

@ashdavies
Copy link
Contributor

ashdavies commented Jun 8, 2020

Hi @binkley, the design of assertThrows is to allow you to build your own CoroutineContext as to not interfere with your test setup, as such I would recommend using the Kotlin Coroutines Test dependency (https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-test#testing-regular-suspend-functions) to run suspended functions under test.

    @Test
    fun `should test suspending functions`() = runBlockingTest {
        assertThrows<IllegalStateException> { failing() }
    }

@williamboman-pp
Copy link

Hey! This does not seem to work with the assertDoesNotThrow function, only wtih assertThrows

@sbrannen
Copy link
Member

@williamboman-pp, this issue was closed over a year ago.

Please open a new issue to address assertDoesNotThrow.

Thanks

@CyxouD
Copy link

CyxouD commented Sep 26, 2023

@ashdavies Doesn't work with Kotlin 1.8.0, JUnit 4.13.2 and kotlinx-coroutines-test 1.5.2 (see screenshot). Maybe there should be assertFailsWith function. Furthermore, runBlockingTest is deprecated
Знімок екрана 2023-09-26 о 11 01 06

For now, I use approach with runBlocking as @DerkSchooltink suggested:

 assertThrows(Error::class.java) {
            runBlocking {
                coroutineFunction()
            }
        }

Another solution is runCatching {}.onFailure { assertThat(..., isInstanceOf(Error::class.java) } as suggested here https://stackoverflow.com/a/63867289/7867180

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment