From f12f1b0f65afca02b1ca00b72d9c0b436ee74783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pi=C4=99ta?= Date: Sat, 19 Mar 2022 21:13:02 +0100 Subject: [PATCH 1/3] Implement ShouldThrowWithMessage --- .../throwable/ThrowableMatchersTest.kt | 338 ++++++++-------- .../CovariantThrowableHandlingTest.kt | 361 ++++++++++-------- .../throwables/CovariantThrowableHandling.kt | 73 +++- 3 files changed, 447 insertions(+), 325 deletions(-) diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/throwable/ThrowableMatchersTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/throwable/ThrowableMatchersTest.kt index 279cca6ee0a..2ae19d47829 100644 --- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/throwable/ThrowableMatchersTest.kt +++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/throwable/ThrowableMatchersTest.kt @@ -3,6 +3,7 @@ package com.sksamuel.kotest.matchers.throwable import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.throwables.shouldThrowAny import io.kotest.assertions.throwables.shouldThrowExactly +import io.kotest.assertions.throwables.shouldThrowWithMessage import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.throwable.shouldHaveCause import io.kotest.matchers.throwable.shouldHaveCauseInstanceOf @@ -16,171 +17,188 @@ import java.io.FileNotFoundException import java.io.IOException class ThrowableMatchersTest : FreeSpec() { - init { - "shouldThrowAny" - { - "shouldHaveMessage" { - shouldThrowAny { throw FileNotFoundException("this_file.txt not found") } shouldHaveMessage "this_file.txt not found" - shouldThrowAny { throw TestException() } shouldHaveMessage "This is a test exception" - shouldThrowAny { throw CompleteTestException() } shouldHaveMessage "This is a complete test exception" - } - "shouldNotHaveMessage" { - shouldThrowAny { throw FileNotFoundException("this_file.txt not found") } shouldNotHaveMessage "random message" - shouldThrowAny { throw TestException() } shouldNotHaveMessage "This is a complete test exception" - shouldThrowAny { throw CompleteTestException() } shouldNotHaveMessage "This is a test exception" - } - "shouldHaveCause" { - shouldThrowAny { throw CompleteTestException() }.shouldHaveCause() - shouldThrowAny { throw CompleteTestException() }.shouldHaveCause { - it shouldHaveMessage "file.txt not found" - } - } - "shouldNotHaveCause" { - shouldThrowAny { throw TestException() }.shouldNotHaveCause() - shouldThrowAny { throw FileNotFoundException("this_file.txt not found") }.shouldNotHaveCause() - } - "shouldHaveCauseInstanceOf" { - shouldThrowAny { throw CompleteTestException() }.shouldHaveCauseInstanceOf() - shouldThrowAny { throw CompleteTestException() }.shouldHaveCauseInstanceOf() - } - "shouldNotHaveCauseInstanceOf" { - shouldThrowAny { throw CompleteTestException() }.shouldNotHaveCauseInstanceOf() - } - "shouldHaveCauseOfType" { - shouldThrowAny { throw CompleteTestException() }.shouldHaveCauseOfType() - } - "shouldNotHaveCauseOfType" { - shouldThrowAny { throw CompleteTestException() }.shouldNotHaveCauseOfType() - } - } - "shouldThrow" - { - "shouldHaveMessage" { - shouldThrow { throw FileNotFoundException("this_file.txt not found") } shouldHaveMessage "this_file.txt not found" - shouldThrow { throw TestException() } shouldHaveMessage "This is a test exception" - shouldThrow { throw CompleteTestException() } shouldHaveMessage "This is a complete test exception" - shouldThrow { TestException() shouldHaveMessage "foo" } - .shouldHaveMessage("""Throwable should have message: + init { + "shouldThrowAny" - { + "shouldHaveMessage" { + shouldThrowAny { throw FileNotFoundException("this_file.txt not found") } shouldHaveMessage "this_file.txt not found" + shouldThrowAny { throw TestException() } shouldHaveMessage "This is a test exception" + shouldThrowAny { throw CompleteTestException() } shouldHaveMessage "This is a complete test exception" + } + "shouldNotHaveMessage" { + shouldThrowAny { throw FileNotFoundException("this_file.txt not found") } shouldNotHaveMessage "random message" + shouldThrowAny { throw TestException() } shouldNotHaveMessage "This is a complete test exception" + shouldThrowAny { throw CompleteTestException() } shouldNotHaveMessage "This is a test exception" + } + "shouldHaveCause" { + shouldThrowAny { throw CompleteTestException() }.shouldHaveCause() + shouldThrowAny { throw CompleteTestException() }.shouldHaveCause { + it shouldHaveMessage "file.txt not found" + } + } + "shouldNotHaveCause" { + shouldThrowAny { throw TestException() }.shouldNotHaveCause() + shouldThrowAny { throw FileNotFoundException("this_file.txt not found") }.shouldNotHaveCause() + } + "shouldHaveCauseInstanceOf" { + shouldThrowAny { throw CompleteTestException() }.shouldHaveCauseInstanceOf() + shouldThrowAny { throw CompleteTestException() }.shouldHaveCauseInstanceOf() + } + "shouldNotHaveCauseInstanceOf" { + shouldThrowAny { throw CompleteTestException() }.shouldNotHaveCauseInstanceOf() + } + "shouldHaveCauseOfType" { + shouldThrowAny { throw CompleteTestException() }.shouldHaveCauseOfType() + } + "shouldNotHaveCauseOfType" { + shouldThrowAny { throw CompleteTestException() }.shouldNotHaveCauseOfType() + } + } + "shouldThrow" - { + "shouldHaveMessage" { + shouldThrow { throw FileNotFoundException("this_file.txt not found") } shouldHaveMessage "this_file.txt not found" + shouldThrow { throw TestException() } shouldHaveMessage "This is a test exception" + shouldThrow { throw CompleteTestException() } shouldHaveMessage "This is a complete test exception" + shouldThrow { TestException() shouldHaveMessage "foo" } + .shouldHaveMessage( + """Throwable should have message: "foo" Actual was: "This is a test exception" -expected:<"foo"> but was:<"This is a test exception">""") - } - "shouldNotHaveMessage" { - shouldThrow { throw FileNotFoundException("this_file.txt not found") } shouldNotHaveMessage "random message" - shouldThrow { throw TestException() } shouldNotHaveMessage "This is a complete test exception" - shouldThrow { throw CompleteTestException() } shouldNotHaveMessage "This is a test exception" - shouldThrow { TestException() shouldNotHaveMessage "This is a test exception" } - .shouldHaveMessage("Throwable should not have message:\n\"This is a test exception\"") - } - "shouldHaveCause" { - shouldThrow { throw CompleteTestException() }.shouldHaveCause() - shouldThrow { throw CompleteTestException() }.shouldHaveCause { - it shouldHaveMessage "file.txt not found" - } - shouldThrow { TestException().shouldHaveCause() } - .shouldHaveMessage("Throwable should have a cause") - } - "shouldNotHaveCause" { - shouldThrow { throw TestException() }.shouldNotHaveCause() - shouldThrow { throw FileNotFoundException("this_file.txt not found") }.shouldNotHaveCause() - shouldThrow { CompleteTestException().shouldNotHaveCause() } - .shouldHaveMessage("Throwable should not have a cause") - } - "shouldHaveCauseInstanceOf" { - shouldThrow { throw CompleteTestException() }.shouldHaveCauseInstanceOf() - shouldThrow { throw CompleteTestException() }.shouldHaveCauseInstanceOf() - shouldThrow { CompleteTestException().shouldHaveCauseInstanceOf() } - .shouldHaveMessage("Throwable cause should be of type java.lang.RuntimeException or it's descendant, but instead got java.io.FileNotFoundException") - } - "shouldNotHaveCauseInstanceOf" { - shouldThrow { throw CompleteTestException() }.shouldNotHaveCauseInstanceOf() - shouldThrow { CompleteTestException().shouldNotHaveCauseInstanceOf() } - .shouldHaveMessage("Throwable cause should not be of type java.io.FileNotFoundException or it's descendant") - } - "shouldHaveCauseOfType" { - shouldThrow { throw CompleteTestException() }.shouldHaveCauseOfType() - shouldThrow { CompleteTestException().shouldHaveCauseOfType() } - .shouldHaveMessage("Throwable cause should be of type java.lang.RuntimeException, but instead got java.io.FileNotFoundException") - } - "shouldNotHaveCauseOfType" { - shouldThrow { throw CompleteTestException() }.shouldNotHaveCauseOfType() - shouldThrow { CompleteTestException().shouldNotHaveCauseOfType() } - .shouldHaveMessage("Throwable cause should not be of type java.io.FileNotFoundException") - } - } - "shouldThrowExactly" - { - "shouldHaveMessage" { - shouldThrowExactly { throw FileNotFoundException("this_file.txt not found") } shouldHaveMessage "this_file.txt not found" - shouldThrowExactly { throw TestException() } shouldHaveMessage "This is a test exception" - shouldThrowExactly { throw CompleteTestException() } shouldHaveMessage "This is a complete test exception" +expected:<"foo"> but was:<"This is a test exception">""" + ) + } + "shouldNotHaveMessage" { + shouldThrow { throw FileNotFoundException("this_file.txt not found") } shouldNotHaveMessage "random message" + shouldThrow { throw TestException() } shouldNotHaveMessage "This is a complete test exception" + shouldThrow { throw CompleteTestException() } shouldNotHaveMessage "This is a test exception" + shouldThrow { TestException() shouldNotHaveMessage "This is a test exception" } + .shouldHaveMessage("Throwable should not have message:\n\"This is a test exception\"") + } + "shouldHaveCause" { + shouldThrow { throw CompleteTestException() }.shouldHaveCause() + shouldThrow { throw CompleteTestException() }.shouldHaveCause { + it shouldHaveMessage "file.txt not found" + } + shouldThrow { TestException().shouldHaveCause() } + .shouldHaveMessage("Throwable should have a cause") + } + "shouldNotHaveCause" { + shouldThrow { throw TestException() }.shouldNotHaveCause() + shouldThrow { throw FileNotFoundException("this_file.txt not found") }.shouldNotHaveCause() + shouldThrow { CompleteTestException().shouldNotHaveCause() } + .shouldHaveMessage("Throwable should not have a cause") + } + "shouldHaveCauseInstanceOf" { + shouldThrow { throw CompleteTestException() }.shouldHaveCauseInstanceOf() + shouldThrow { throw CompleteTestException() }.shouldHaveCauseInstanceOf() + shouldThrow { CompleteTestException().shouldHaveCauseInstanceOf() } + .shouldHaveMessage("Throwable cause should be of type java.lang.RuntimeException or it's descendant, but instead got java.io.FileNotFoundException") + } + "shouldNotHaveCauseInstanceOf" { + shouldThrow { throw CompleteTestException() }.shouldNotHaveCauseInstanceOf() + shouldThrow { CompleteTestException().shouldNotHaveCauseInstanceOf() } + .shouldHaveMessage("Throwable cause should not be of type java.io.FileNotFoundException or it's descendant") + } + "shouldHaveCauseOfType" { + shouldThrow { throw CompleteTestException() }.shouldHaveCauseOfType() + shouldThrow { CompleteTestException().shouldHaveCauseOfType() } + .shouldHaveMessage("Throwable cause should be of type java.lang.RuntimeException, but instead got java.io.FileNotFoundException") + } + "shouldNotHaveCauseOfType" { + shouldThrow { throw CompleteTestException() }.shouldNotHaveCauseOfType() + shouldThrow { CompleteTestException().shouldNotHaveCauseOfType() } + .shouldHaveMessage("Throwable cause should not be of type java.io.FileNotFoundException") + } + } + "shouldThrowExactly" - { + "shouldHaveMessage" { + shouldThrowExactly { throw FileNotFoundException("this_file.txt not found") } shouldHaveMessage "this_file.txt not found" + shouldThrowExactly { throw TestException() } shouldHaveMessage "This is a test exception" + shouldThrowExactly { throw CompleteTestException() } shouldHaveMessage "This is a complete test exception" + } + "shouldNotHaveMessage" { + shouldThrowExactly { throw FileNotFoundException("this_file.txt not found") } shouldNotHaveMessage "random message" + shouldThrowExactly { throw TestException() } shouldNotHaveMessage "This is a complete test exception" + shouldThrowExactly { throw CompleteTestException() } shouldNotHaveMessage "This is a test exception" + } + "shouldHaveCause" { + shouldThrowExactly { throw CompleteTestException() }.shouldHaveCause() + shouldThrowExactly { throw CompleteTestException() }.shouldHaveCause { + it shouldHaveMessage "file.txt not found" + } + } + "shouldNotHaveCause" { + shouldThrowExactly { throw TestException() }.shouldNotHaveCause() + shouldThrowExactly { throw FileNotFoundException("this_file.txt not found") }.shouldNotHaveCause() + } + "shouldHaveCauseInstanceOf" { + shouldThrowExactly { throw CompleteTestException() }.shouldHaveCauseInstanceOf() + shouldThrowExactly { throw CompleteTestException() }.shouldHaveCauseInstanceOf() + } + "shouldNotHaveCauseInstanceOf" { + shouldThrowExactly { throw CompleteTestException() }.shouldNotHaveCauseInstanceOf() + } + "shouldHaveCauseOfType" { + shouldThrowExactly { throw CompleteTestException() }.shouldHaveCauseOfType() + } + "shouldNotHaveCauseOfType" { + shouldThrowExactly { throw CompleteTestException() }.shouldNotHaveCauseOfType() + } + } + "result" - { + "shouldHaveMessage" { + Result.failure(FileNotFoundException("this_file.txt not found")) + .exceptionOrNull()!! shouldHaveMessage "this_file.txt not found" + Result.failure(TestException()).exceptionOrNull()!! shouldHaveMessage "This is a test exception" + Result.failure(CompleteTestException()) + .exceptionOrNull()!! shouldHaveMessage "This is a complete test exception" + } + "shouldNotHaveMessage" { + Result.failure(FileNotFoundException("this_file.txt not found")) + .exceptionOrNull()!! shouldNotHaveMessage "random message" + Result.failure(TestException()) + .exceptionOrNull()!! shouldNotHaveMessage "This is a complete test exception" + Result.failure(CompleteTestException()) + .exceptionOrNull()!! shouldNotHaveMessage "This is a test exception" + } + "shouldHaveCause" { + Result.failure(CompleteTestException()).exceptionOrNull()!!.shouldHaveCause() + Result.failure(CompleteTestException()).exceptionOrNull()!!.shouldHaveCause { + it shouldHaveMessage "file.txt not found" + } + } + "shouldNotHaveCause" { + Result.failure(TestException()).exceptionOrNull()!!.shouldNotHaveCause() + Result.failure(FileNotFoundException("this_file.txt not found")).exceptionOrNull()!! + .shouldNotHaveCause() + } + "shouldHaveCauseInstanceOf" { + Result.failure(CompleteTestException()).exceptionOrNull()!! + .shouldHaveCauseInstanceOf() + Result.failure(CompleteTestException()).exceptionOrNull()!!.shouldHaveCauseInstanceOf() + } + "shouldNotHaveCauseInstanceOf" { + Result.failure(CompleteTestException()).exceptionOrNull()!! + .shouldNotHaveCauseInstanceOf() + } + "shouldHaveCauseOfType" { + Result.failure(CompleteTestException()).exceptionOrNull()!! + .shouldHaveCauseOfType() + } + "shouldNotHaveCauseOfType" { + Result.failure(CompleteTestException()).exceptionOrNull()!!.shouldNotHaveCauseOfType() + } } - "shouldNotHaveMessage" { - shouldThrowExactly { throw FileNotFoundException("this_file.txt not found") } shouldNotHaveMessage "random message" - shouldThrowExactly { throw TestException() } shouldNotHaveMessage "This is a complete test exception" - shouldThrowExactly { throw CompleteTestException() } shouldNotHaveMessage "This is a test exception" - } - "shouldHaveCause" { - shouldThrowExactly { throw CompleteTestException() }.shouldHaveCause() - shouldThrowExactly { throw CompleteTestException() }.shouldHaveCause { - it shouldHaveMessage "file.txt not found" - } - } - "shouldNotHaveCause" { - shouldThrowExactly { throw TestException() }.shouldNotHaveCause() - shouldThrowExactly { throw FileNotFoundException("this_file.txt not found") }.shouldNotHaveCause() - } - "shouldHaveCauseInstanceOf" { - shouldThrowExactly { throw CompleteTestException() }.shouldHaveCauseInstanceOf() - shouldThrowExactly { throw CompleteTestException() }.shouldHaveCauseInstanceOf() - } - "shouldNotHaveCauseInstanceOf" { - shouldThrowExactly { throw CompleteTestException() }.shouldNotHaveCauseInstanceOf() - } - "shouldHaveCauseOfType" { - shouldThrowExactly { throw CompleteTestException() }.shouldHaveCauseOfType() - } - "shouldNotHaveCauseOfType" { - shouldThrowExactly { throw CompleteTestException() }.shouldNotHaveCauseOfType() - } - } - "result" - { - "shouldHaveMessage" { - Result.failure(FileNotFoundException("this_file.txt not found")).exceptionOrNull()!! shouldHaveMessage "this_file.txt not found" - Result.failure(TestException()).exceptionOrNull()!! shouldHaveMessage "This is a test exception" - Result.failure(CompleteTestException()).exceptionOrNull()!! shouldHaveMessage "This is a complete test exception" - } - "shouldNotHaveMessage" { - Result.failure(FileNotFoundException("this_file.txt not found")).exceptionOrNull()!! shouldNotHaveMessage "random message" - Result.failure(TestException()).exceptionOrNull()!! shouldNotHaveMessage "This is a complete test exception" - Result.failure(CompleteTestException()).exceptionOrNull()!! shouldNotHaveMessage "This is a test exception" - } - "shouldHaveCause" { - Result.failure(CompleteTestException()).exceptionOrNull()!!.shouldHaveCause() - Result.failure(CompleteTestException()).exceptionOrNull()!!.shouldHaveCause { - it shouldHaveMessage "file.txt not found" - } - } - "shouldNotHaveCause" { - Result.failure(TestException()).exceptionOrNull()!!.shouldNotHaveCause() - Result.failure(FileNotFoundException("this_file.txt not found")).exceptionOrNull()!!.shouldNotHaveCause() - } - "shouldHaveCauseInstanceOf" { - Result.failure(CompleteTestException()).exceptionOrNull()!!.shouldHaveCauseInstanceOf() - Result.failure(CompleteTestException()).exceptionOrNull()!!.shouldHaveCauseInstanceOf() - } - "shouldNotHaveCauseInstanceOf" { - Result.failure(CompleteTestException()).exceptionOrNull()!!.shouldNotHaveCauseInstanceOf() - } - "shouldHaveCauseOfType" { - Result.failure(CompleteTestException()).exceptionOrNull()!!.shouldHaveCauseOfType() - } - "shouldNotHaveCauseOfType" { - Result.failure(CompleteTestException()).exceptionOrNull()!!.shouldNotHaveCauseOfType() - } - } - } + "shouldThrowWithMessage" { + shouldThrowWithMessage("This is a test exception") { + throw TestException() + } shouldHaveMessage "This is a test exception" + } + } - class TestException : Throwable("This is a test exception") - class CompleteTestException : Throwable("This is a complete test exception", FileNotFoundException("file.txt not found")) + class TestException : Throwable("This is a test exception") + class CompleteTestException : + Throwable("This is a complete test exception", FileNotFoundException("file.txt not found")) } diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt index 05fce81666c..9596ad20c0c 100644 --- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt +++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt @@ -4,6 +4,8 @@ import io.kotest.assertions.throwables.shouldNotThrow import io.kotest.assertions.throwables.shouldNotThrowUnit import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.throwables.shouldThrowUnit +import io.kotest.assertions.throwables.shouldThrowUnitWithMessage +import io.kotest.assertions.throwables.shouldThrowWithMessage import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.booleans.shouldBeTrue import io.kotest.matchers.shouldBe @@ -13,202 +15,235 @@ import kotlin.reflect.KClass class CovariantThrowableHandlingTest : FreeSpec() { - private class AssertionErrorSubclass : AssertionError() - - init { - - "Should throw" - { - "Should throw a new exception" - { - "When no exception is thrown" { - onShouldThrowMatcher { shouldThrowMatcher -> - verifyNoExceptionThrownError(FooRuntimeException::class) { - shouldThrowMatcher { /* No exception is thrown */ } + private class AssertionErrorSubclass : AssertionError() + + init { + "Should throw" - { + "Should throw a new exception" - { + "When no exception is thrown" { + onShouldThrowMatcher { shouldThrowMatcher -> + verifyNoExceptionThrownError(FooRuntimeException::class) { + shouldThrowMatcher { /* No exception is thrown */ } + } + } } - } - } - "When an exception is thrown, but it's not the right class" { - val instanceToThrow = NullPointerException() - - onShouldThrowMatcher { shouldThrowMatcher -> - verifyThrowsWrongExceptionClass(instanceToThrow, FooRuntimeException::class, NullPointerException::class) { - shouldThrowMatcher { throw instanceToThrow } + "When an exception is thrown, but it's not the right class" { + val instanceToThrow = NullPointerException() + + onShouldThrowMatcher { shouldThrowMatcher -> + verifyThrowsWrongExceptionClass( + instanceToThrow, + FooRuntimeException::class, + NullPointerException::class + ) { + shouldThrowMatcher { throw instanceToThrow } + } + } } - } - } - "When an exception is thrown, but it's the parent class of the right class" { - val instanceToThrow = ParentException() + "When an exception is thrown, but it's the parent class of the right class" { + val instanceToThrow = ParentException() - onShouldThrowMatcher { shouldThrowMatcher -> - verifyThrowsWrongExceptionClass(instanceToThrow, SubException::class, ParentException::class) { - shouldThrowMatcher { throw instanceToThrow } + onShouldThrowMatcher { shouldThrowMatcher -> + verifyThrowsWrongExceptionClass(instanceToThrow, SubException::class, ParentException::class) { + shouldThrowMatcher { throw instanceToThrow } + } + } } - } - } - } + } - "Should throw the thrown exception" - { - "When an exception is thrown, but it's an Assertion Error (special case) when it's not expected" { - val thrownInstance = AssertionError() + "Should throw the thrown exception" - { + "When an exception is thrown, but it's an Assertion Error (special case) when it's not expected" { + val thrownInstance = AssertionError() - onShouldThrowMatcher { shouldThrowMatcher -> - verifyThrowsAssertionErrorInstance(thrownInstance) { - shouldThrowMatcher { throw thrownInstance } + onShouldThrowMatcher { shouldThrowMatcher -> + verifyThrowsAssertionErrorInstance(thrownInstance) { + shouldThrowMatcher { throw thrownInstance } + } + } } - } - } - } - - "Should return the thrown exception" - { - "When an exception is thrown and it's exactly the right class" { - val thrownException = FooRuntimeException() - onShouldThrowMatcher { shouldThrowMatcher -> - verifyReturnsExactly(thrownException) { - shouldThrowMatcher { throw thrownException } + } + + "Should return the thrown exception" - { + "When an exception is thrown and it's exactly the right class" { + val thrownException = FooRuntimeException() + onShouldThrowMatcher { shouldThrowMatcher -> + verifyReturnsExactly(thrownException) { + shouldThrowMatcher { throw thrownException } + } + } } - } - } - - "When an exception is thrown and it's a subclass of the right class" { - val thrownException = SubException() - onShouldThrowMatcher { shouldThrowMatcher -> - verifyReturnsExactly(thrownException) { - shouldThrowMatcher { throw thrownException } + + "When an exception is thrown and it's a subclass of the right class" { + val thrownException = SubException() + onShouldThrowMatcher { shouldThrowMatcher -> + verifyReturnsExactly(thrownException) { + shouldThrowMatcher { throw thrownException } + } + } } - } - } - - "When an AssertionError is thrown and it's exactly the right class" { - val thrownException = AssertionError() - onShouldThrowMatcher { shouldThrowMatcher -> - verifyReturnsExactly(thrownException) { - shouldThrowMatcher { throw thrownException } + + "When an AssertionError is thrown and it's exactly the right class" { + val thrownException = AssertionError() + onShouldThrowMatcher { shouldThrowMatcher -> + verifyReturnsExactly(thrownException) { + shouldThrowMatcher { throw thrownException } + } + } } - } - } - - "When a subclass of AssertionError is thrown, and we're expecting an AssertionError" { - val thrownException = AssertionErrorSubclass() - onShouldThrowMatcher { shouldThrowMatcher -> - verifyReturnsExactly(thrownException) { - shouldThrowMatcher { throw thrownException } + + "When a subclass of AssertionError is thrown, and we're expecting an AssertionError" { + val thrownException = AssertionErrorSubclass() + onShouldThrowMatcher { shouldThrowMatcher -> + verifyReturnsExactly(thrownException) { + shouldThrowMatcher { throw thrownException } + } + } } - } - } + } } - } - "Should not throw" - { - "Should throw an assertion error wrapping the thrown exception" - { - "When it's an instance of the right class" { - val thrownException = FooRuntimeException() + "Should not throw" - { + "Should throw an assertion error wrapping the thrown exception" - { + "When it's an instance of the right class" { + val thrownException = FooRuntimeException() - onShouldNotThrowMatcher { shouldNotThrowMatcher -> - verifyThrowsAssertionWrapping(thrownException) { - shouldNotThrowMatcher { throw thrownException } + onShouldNotThrowMatcher { shouldNotThrowMatcher -> + verifyThrowsAssertionWrapping(thrownException) { + shouldNotThrowMatcher { throw thrownException } + } + } } - } - } - "When it's a subclass of the right class" { - val thrownException = SubException() + "When it's a subclass of the right class" { + val thrownException = SubException() - onShouldNotThrowMatcher { shouldNotThrowMatcher -> + onShouldNotThrowMatcher { shouldNotThrowMatcher -> - verifyThrowsAssertionWrapping(thrownException) { - shouldNotThrowMatcher { throw thrownException } + verifyThrowsAssertionWrapping(thrownException) { + shouldNotThrowMatcher { throw thrownException } + } + } } - } - } - } + } - "Should throw the thrown exception" - { - "When it's not an instance nor subclass of the right class" { - val thrownException = FooRuntimeException() + "Should throw the thrown exception" - { + "When it's not an instance nor subclass of the right class" { + val thrownException = FooRuntimeException() - onShouldNotThrowMatcher { shouldNotThrowMatcher -> - verifyThrowsExactly(thrownException) { - shouldNotThrowMatcher { throw thrownException } + onShouldNotThrowMatcher { shouldNotThrowMatcher -> + verifyThrowsExactly(thrownException) { + shouldNotThrowMatcher { throw thrownException } + } + } } - } - } - "When it's an instance of the parent class of the right class" { - val thrownException = ParentException() + "When it's an instance of the parent class of the right class" { + val thrownException = ParentException() - onShouldNotThrowMatcher { shouldNotThrowMatcher -> - verifyThrowsExactly(thrownException) { - shouldNotThrowMatcher { throw thrownException } + onShouldNotThrowMatcher { shouldNotThrowMatcher -> + verifyThrowsExactly(thrownException) { + shouldNotThrowMatcher { throw thrownException } + } + } } - } - } - } + } - "Should not throw an exception" - { - "When no exception is thrown" { - onShouldNotThrowMatcher { shouldNotThrowMatcher -> - val thrown = catchThrowable { - shouldNotThrowMatcher { /* Nothing thrown */ } - } + "Should not throw an exception" - { + "When no exception is thrown" { + onShouldNotThrowMatcher { shouldNotThrowMatcher -> + val thrown = catchThrowable { + shouldNotThrowMatcher { /* Nothing thrown */ } + } - thrown shouldBe null - } - } + thrown shouldBe null + } + } + } } - } - } - private inline fun onShouldThrowMatcher(func: (ShouldThrowMatcher) -> Unit) { - func(::shouldThrowUnit) - func { shouldThrow(it) } - } - - private fun verifyNoExceptionThrownError(expectedClass: KClass<*>, block: () -> Unit) { - val throwable = catchThrowable(block) - - throwable.shouldBeInstanceOf() - throwable.message shouldBe "Expected exception ${expectedClass.qualifiedName} but no exception was thrown." - } - - private fun verifyThrowsAssertionErrorInstance(assertionErrorInstance: AssertionError, block: () -> Unit) { - val throwable = catchThrowable(block) - (throwable === assertionErrorInstance).shouldBeTrue() - } - - private fun verifyThrowsWrongExceptionClass(thrownInstance: Throwable, expectedClass: KClass<*>, incorrectClass: KClass<*>, block: () -> Unit) { - val throwable = catchThrowable(block) - - throwable.shouldBeInstanceOf() - throwable.message shouldBe "Expected exception ${expectedClass.qualifiedName} but a ${incorrectClass.simpleName} was thrown instead." - (throwable.cause === thrownInstance).shouldBeTrue() - } - - private fun verifyReturnsExactly(thrownException: Throwable, block: () -> Any?) { - val actualReturn = block() - - (thrownException === actualReturn).shouldBeTrue() - } - - private inline fun onShouldNotThrowMatcher(func: (ShouldNotThrowMatcher) -> Unit) { - func { shouldNotThrowUnit { it() } } - func { shouldNotThrow(it) } - } - - private fun verifyThrowsAssertionWrapping(exception: Throwable, block: () -> Unit) { - val thrown = catchThrowable(block) - - thrown.shouldBeInstanceOf() - thrown.message shouldBe "No exception expected, but a ${exception::class.simpleName} was thrown." - thrown.cause shouldBeSameInstanceAs exception - } - - private fun verifyThrowsExactly(exception: Throwable, block: () -> Unit) { - val thrown = catchThrowable(block) - thrown shouldBeSameInstanceAs exception - } + "should throw with message, wrong" { + val exception = Exception("bar") + onShouldThrowWithMessageMatcher("foo") { shouldThrowMatcher -> + verifyThrowsWrongExceptionMessage(exception, "foo", "bar") { shouldThrowMatcher { throw exception } } + } + } + } + + private fun onShouldThrowWithMessageMatcher(message: String, func: (ShouldThrowMatcher) -> Unit) { + func { shouldThrowUnitWithMessage(message, it) } + func { shouldThrowWithMessage(message, it) } + } + + private inline fun onShouldThrowMatcher(func: (ShouldThrowMatcher) -> Unit) { + func(::shouldThrowUnit) + func { shouldThrow(it) } + } + + private fun verifyNoExceptionThrownError(expectedClass: KClass<*>, block: () -> Unit) { + val throwable = catchThrowable(block) + + throwable.shouldBeInstanceOf() + throwable.message shouldBe "Expected exception ${expectedClass.qualifiedName} but no exception was thrown." + } + + private fun verifyThrowsAssertionErrorInstance(assertionErrorInstance: AssertionError, block: () -> Unit) { + val throwable = catchThrowable(block) + (throwable === assertionErrorInstance).shouldBeTrue() + } + + private fun verifyThrowsWrongExceptionClass( + thrownInstance: Throwable, + expectedClass: KClass<*>, + incorrectClass: KClass<*>, + block: () -> Unit + ) { + val throwable = catchThrowable(block) + + throwable.shouldBeInstanceOf() + throwable.message shouldBe "Expected exception ${expectedClass.qualifiedName} but a ${incorrectClass.simpleName} was thrown instead." + (throwable.cause === thrownInstance).shouldBeTrue() + } + + private fun verifyThrowsWrongExceptionMessage( + thrownInstance: Throwable, + expectedMessage: String, + actualMessage: String, + block: () -> Unit + ) { + val throwable = catchThrowable(block) + + throwable.shouldBeInstanceOf() + throwable.message shouldBe "Expected exception message $expectedMessage but was $actualMessage instead." + (throwable.cause === thrownInstance).shouldBeTrue() + } + + private fun verifyReturnsExactly(thrownException: Throwable, block: () -> Any?) { + val actualReturn = block() + + (thrownException === actualReturn).shouldBeTrue() + } + + private inline fun onShouldNotThrowMatcher(func: (ShouldNotThrowMatcher) -> Unit) { + func { shouldNotThrowUnit { it() } } + func { shouldNotThrow(it) } + } + + private fun verifyThrowsAssertionWrapping(exception: Throwable, block: () -> Unit) { + val thrown = catchThrowable(block) + + thrown.shouldBeInstanceOf() + thrown.message shouldBe "No exception expected, but a ${exception::class.simpleName} was thrown." + thrown.cause shouldBeSameInstanceAs exception + } + + private fun verifyThrowsExactly(exception: Throwable, block: () -> Unit) { + val thrown = catchThrowable(block) + thrown shouldBeSameInstanceAs exception + } } private typealias ShouldThrowMatcher = (() -> Unit) -> T diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/throwables/CovariantThrowableHandling.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/throwables/CovariantThrowableHandling.kt index 1ba11840bc3..0625bfb4637 100644 --- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/throwables/CovariantThrowableHandling.kt +++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/throwables/CovariantThrowableHandling.kt @@ -31,6 +31,39 @@ import io.kotest.mpp.bestName */ inline fun shouldThrowUnit(block: () -> Unit): T = shouldThrow { block() } +/** + * Verifies if a block of code throws a Throwable of type [T] or subtypes with specified message + * + * Use this function to wrap a block of code that you'd like to verify whether it throws [T] (or subclasses) or not. + * + * This should be used when [shouldThrowWithMessage] can't be used, such as when doing assignments (assignments are statements, + * therefore has no return value). + * + * This function will include all subclasses of [T]. For example, if you test for [java.io.IOException] and + * the code block throws [java.io.FileNotFoundException], the test will pass. + * + * If you wish to test for a specific class strictly (excluding subclasses), use [shouldThrowExactlyUnit] instead. + * + * If you don't care about the thrown exception type, use [shouldThrowAnyUnit]. + * + * + * ``` + * val thrownException: FooException = shouldThrowUnit { + * // Code that we expect to throw FooException + * throw FooException() + * } + * ``` + * + * @see [shouldThrowWithMessage] + */ +inline fun shouldThrowUnitWithMessage(message: String, block: () -> Unit): T = + shouldThrowUnit(block).let { + when (it.message) { + message -> it + else -> throw failure("Expected exception message $message but was ${it.message} instead.", it) + } + } + /** * Verifies that a block of code doesn't throw a Throwable of type [T] or subtypes * @@ -100,11 +133,47 @@ inline fun shouldThrow(block: () -> Any?): T { null -> throw failure("Expected exception ${expectedExceptionClass.bestName()} but no exception was thrown.") is T -> thrownThrowable // This should be before `is AssertionError`. If the user is purposefully trying to verify `shouldThrow{}` this will take priority is AssertionError -> throw thrownThrowable - else -> throw failure("Expected exception ${expectedExceptionClass.bestName()} but a ${thrownThrowable::class.simpleName} was thrown instead.", - thrownThrowable) + else -> throw failure( + "Expected exception ${expectedExceptionClass.bestName()} but a ${thrownThrowable::class.simpleName} was thrown instead.", + thrownThrowable + ) } } +/** + * Verifies if a block of code will throw a Throwable of type [T] or subtypes with specified message. + * + * Use this function to wrap a block of code that you'd like to verify whether it throws [T] (or subclasses) or not. + * + * This function will include subclasses of [T]. For example, if you test for [java.io.IOException] and + * the code block throws [java.io.FileNotFoundException], the test will pass. + * + * If you wish to test for a specific class strictly (excluding subclasses), use [shouldThrowExactly] instead. + * + * If you don't care about which exception is thrown, then use [shouldThrowAny]. + * + * **Attention to assignment operations**: + * + * When doing an assignment to a variable, the code won't compile, because an assignment is not of type [Any], as required + * by [block]. If you need to test that an assignment throws a [Throwable], use [shouldThrowUnitWithMessage] or it's variations. + * + * ``` + * val thrownException: FooException = shouldThrow { + * // Code that we expect to throw FooException + * throw FooException() + * } + * ``` + * + * @see [shouldThrowUnitWithMessage] + */ +inline fun shouldThrowWithMessage(message: String, block: () -> Any?): T = + shouldThrow(block).let { + when (it.message) { + message -> it + else -> throw failure("Expected exception message $message but was ${it.message} instead.", it.cause) + } + } + /** * Verifies that a block of code will not throw a Throwable of type [T] or subtypes * From 67805cce0033ce00866e0e8f4b0bc5abe79e0677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pi=C4=99ta?= Date: Sat, 19 Mar 2022 21:39:59 +0100 Subject: [PATCH 2/3] Fix typo --- .../kotest/throwablehandling/CovariantThrowableHandlingTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt index 9596ad20c0c..e9a788316ab 100644 --- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt +++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt @@ -165,7 +165,7 @@ class CovariantThrowableHandlingTest : FreeSpec() { } } - "should throw with message, wrong" { + "should throw with message" { val exception = Exception("bar") onShouldThrowWithMessageMatcher("foo") { shouldThrowMatcher -> verifyThrowsWrongExceptionMessage(exception, "foo", "bar") { shouldThrowMatcher { throw exception } } From 10d8213b5e86a160a6a0b239dc3b9c4715f7d3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pi=C4=99ta?= Date: Sun, 20 Mar 2022 12:02:51 +0100 Subject: [PATCH 3/3] Fix test --- .../CovariantThrowableHandlingTest.kt | 23 ++++++++++++++----- .../throwables/CovariantThrowableHandling.kt | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt index e9a788316ab..3696861632c 100644 --- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt +++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/throwablehandling/CovariantThrowableHandlingTest.kt @@ -165,10 +165,23 @@ class CovariantThrowableHandlingTest : FreeSpec() { } } - "should throw with message" { - val exception = Exception("bar") - onShouldThrowWithMessageMatcher("foo") { shouldThrowMatcher -> - verifyThrowsWrongExceptionMessage(exception, "foo", "bar") { shouldThrowMatcher { throw exception } } + "should throw with message" - { + "When the correct exception is thrown, but the message is wrong" { + onShouldThrowWithMessageMatcher("foo") { shouldThrowMatcher -> + verifyThrowsWrongExceptionMessage("foo", "bar") { + shouldThrowMatcher { throw Exception("bar") } + } + } + } + "Exception class type should have priority in assertion" { + val instanceToThrow = Exception("foo") + + runCatching { + shouldThrowWithMessage("bar") { + throw instanceToThrow + } + } + .exceptionOrNull() shouldBe AssertionError("Expected exception java.lang.RuntimeException but a Exception was thrown instead.") } } } @@ -209,7 +222,6 @@ class CovariantThrowableHandlingTest : FreeSpec() { } private fun verifyThrowsWrongExceptionMessage( - thrownInstance: Throwable, expectedMessage: String, actualMessage: String, block: () -> Unit @@ -218,7 +230,6 @@ class CovariantThrowableHandlingTest : FreeSpec() { throwable.shouldBeInstanceOf() throwable.message shouldBe "Expected exception message $expectedMessage but was $actualMessage instead." - (throwable.cause === thrownInstance).shouldBeTrue() } private fun verifyReturnsExactly(thrownException: Throwable, block: () -> Any?) { diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/throwables/CovariantThrowableHandling.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/throwables/CovariantThrowableHandling.kt index 0625bfb4637..bd7224ecf65 100644 --- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/throwables/CovariantThrowableHandling.kt +++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/throwables/CovariantThrowableHandling.kt @@ -170,7 +170,7 @@ inline fun shouldThrowWithMessage(message: String, block shouldThrow(block).let { when (it.message) { message -> it - else -> throw failure("Expected exception message $message but was ${it.message} instead.", it.cause) + else -> throw failure("Expected exception message $message but was ${it.message} instead.", it) } }