All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmTest.com.sksamuel.kotest.throwablehandling.StrictThrowableHandlingTest.kt Maven / Gradle / Ivy

package com.sksamuel.kotest.throwablehandling

import io.kotest.assertions.throwables.shouldNotThrowExactly
import io.kotest.assertions.throwables.shouldNotThrowExactlyUnit
import io.kotest.assertions.throwables.shouldThrowExactly
import io.kotest.assertions.throwables.shouldThrowExactlyUnit
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.shouldBeInstanceOf
import io.kotest.matchers.types.shouldBeSameInstanceAs
import kotlin.reflect.KClass

class StrictThrowableHandlingTest : FreeSpec() {

  init {
    "Should throw exactly" - {
      "Should throw a new exception" - {
        "When no exception is thrown" {
          onShouldThrowExactlyMatcher { shouldThrowExactlyMatcher ->
            verifyNoExceptionThrownError(FooRuntimeException::class) {
              shouldThrowExactlyMatcher { /* No exception is thrown */ }
            }
          }
        }

        "When an exception is thrown, but it's not the right class" {
          val instanceToThrow = NullPointerException()

          onShouldThrowExactlyMatcher { shouldThrowExactlyMatcher ->
            verifyThrowsWrongExceptionClass(instanceToThrow, FooRuntimeException::class, NullPointerException::class) {
              shouldThrowExactlyMatcher { throw instanceToThrow }
            }
          }
        }

        "When an exception is thrown, but it's the parent class of the right class" {
          val instanceToThrow = ParentException()

          onShouldThrowExactlyMatcher { shouldThrowExactlyMatcher ->
            verifyThrowsWrongExceptionClass(instanceToThrow, SubException::class, ParentException::class) {
              shouldThrowExactlyMatcher { throw instanceToThrow }
            }
          }
        }

        "When an exception is thrown, but it's subclass of the right class" {
          val instanceToThrow = SubException()
          onShouldThrowExactlyMatcher { shouldThrowExactlyMatcher ->
            verifyThrowsWrongExceptionClass(instanceToThrow, ParentException::class, SubException::class) {
              shouldThrowExactlyMatcher { throw instanceToThrow }
            }
          }
        }
      }

      "Should throw the thrown exception" - {
        "When an exception is thrown, and it's an Assertion Error (special case) when it's not the expected error" {
          val thrownInstance = AssertionError()

          onShouldThrowExactlyMatcher { shouldThrowExactlyMatcher ->
            verifyThrowsAssertionErrorInstance(thrownInstance) {
              shouldThrowExactlyMatcher { throw thrownInstance }
            }
          }
        }
      }

      "Should return the thrown exception" - {
        "When an exception is thrown, and it's exactly the right class" {
          val thrownException = FooRuntimeException()
          onShouldThrowExactlyMatcher { shouldThrowExactlyMatcher ->
            verifyReturnsExactly(thrownException) {
              shouldThrowExactlyMatcher { throw thrownException }
            }
          }
        }

        "When an AssertionError is thrown, and it's exactly the right class" {
          val thrownException = AssertionError()
          onShouldThrowExactlyMatcher { shouldThrowExactlyMatcher ->
            verifyReturnsExactly(thrownException) {
              shouldThrowExactlyMatcher { throw thrownException }
            }
          }
        }
      }
    }

    "Should not throw exactly" - {
      "Should throw an assertion error wrapping the thrown exception" - {
        "When the exact class is thrown" {
          val thrownException = FooRuntimeException()

          onShouldNotThrowExactlyMatcher { shouldNotThrowExactlyMatcher ->
            verifyThrowsAssertionWrapping(thrownException) {
              shouldNotThrowExactlyMatcher { throw thrownException }
            }
          }
        }
      }

      "Should throw the thrown exception" - {
        "When it's a subclass of the expected type" {
          val thrownException = SubException()

          onShouldNotThrowExactlyMatcher { shouldNotThrowExactlyMatcher ->
            verifyThrowsExactly(thrownException) {
              shouldNotThrowExactlyMatcher { throw thrownException }
            }
          }
        }

        "When it's a super class of the expected type" {
          val thrownException = ParentException()

          onShouldNotThrowExactlyMatcher { shouldNotThrowExactlyMatcher ->
            verifyThrowsExactly(thrownException) {
              shouldNotThrowExactlyMatcher { throw thrownException }
            }
          }
        }

        "When it's unrelated to the expected type" {
          val thrownException = FooRuntimeException()

          onShouldNotThrowExactlyMatcher { shouldNotThrowExactlyMatcher ->
            verifyThrowsExactly(thrownException) {
              shouldNotThrowExactlyMatcher { throw thrownException }
            }
          }
        }
      }

      "Should not throw anything" - {
        "When nothing is thrown" {

          onShouldNotThrowExactlyMatcher { shouldNotThrowExactlyMatcher ->
            verifyNoErrorIsThrown {
              shouldNotThrowExactlyMatcher { /* Success */ }
            }
          }
        }
      }
    }
  }

  private inline fun  onShouldThrowExactlyMatcher(func: (ShouldThrowExactlyMatcher) -> Unit) {
    func(::shouldThrowExactlyUnit)
    func { shouldThrowExactly(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  onShouldNotThrowExactlyMatcher(func: (ShouldNotThrowExactlyMatcher) -> Unit) {
    func { shouldNotThrowExactly(it) }
    func { shouldNotThrowExactlyUnit(it) }
  }

  private fun verifyThrowsAssertionWrapping(thrownException: FooRuntimeException, block: () -> Unit) {
    val thrown = catchThrowable(block)

    thrown!!.shouldBeInstanceOf()
    thrown.message shouldBe "No exception expected, but a FooRuntimeException was thrown."
    thrown.cause shouldBeSameInstanceAs thrownException

  }

  private fun verifyThrowsExactly(thrownException: Throwable, block: () -> Unit) {
    catchThrowable(block).shouldBeSameInstanceAs(thrownException)
  }

  private fun verifyNoErrorIsThrown(block: () -> Unit) {
    block()
  }

}

private typealias ShouldThrowExactlyMatcher = (() -> Unit) -> T
private typealias ShouldNotThrowExactlyMatcher = (() -> Unit) -> Unit




© 2015 - 2025 Weber Informatics LLC | Privacy Policy