Common test assertions and utilities for Kotlin multiplatform projects
—
Core interfaces and utilities for integrating with different testing frameworks across Kotlin multiplatform targets. This system provides a pluggable architecture that allows the same test code to work with JUnit, TestNG, JavaScript testing frameworks, and native testing environments.
The core interface that abstracts assertion logic and provides integration points for different testing frameworks.
/**
* Interface that abstracts the assertion logic and can be implemented by
* any testing framework to provide consistent assertion behavior across platforms.
*/
interface Asserter {
/**
* Marks a test as failed with the specified message.
* @param message The failure message, or null for default message
* @return Nothing (this function never returns normally)
*/
fun fail(message: String?): Nothing
/**
* Marks a test as failed with the specified message and cause.
* @param message The failure message, or null for default message
* @param cause The underlying cause of the failure, or null if none
* @return Nothing (this function never returns normally)
*/
fun fail(message: String?, cause: Throwable?): Nothing
/**
* Asserts that the value is true.
* @param lazyMessage Lazy-evaluated message provider for failure cases
* @param actual The boolean value to test
*/
fun assertTrue(lazyMessage: () -> String?, actual: Boolean)
/**
* Asserts that the value is true.
* @param message The failure message, or null for default
* @param actual The boolean value to test
*/
fun assertTrue(message: String?, actual: Boolean)
/**
* Asserts that the expected and actual values are equal.
* @param message The failure message, or null for default
* @param expected The expected value
* @param actual The actual value to compare
*/
fun assertEquals(message: String?, expected: Any?, actual: Any?)
/**
* Asserts that the illegal and actual values are not equal.
* @param message The failure message, or null for default
* @param illegal The value that should not match
* @param actual The actual value to compare
*/
fun assertNotEquals(message: String?, illegal: Any?, actual: Any?)
/**
* Asserts that the expected and actual references are the same instance.
* @param message The failure message, or null for default
* @param expected The expected object reference
* @param actual The actual object reference
*/
fun assertSame(message: String?, expected: Any?, actual: Any?)
/**
* Asserts that the illegal and actual references are not the same instance.
* @param message The failure message, or null for default
* @param illegal The reference that should not match
* @param actual The actual object reference
*/
fun assertNotSame(message: String?, illegal: Any?, actual: Any?)
/**
* Asserts that the actual value is null.
* @param message The failure message, or null for default
* @param actual The value to test for null
*/
fun assertNull(message: String?, actual: Any?)
/**
* Asserts that the actual value is not null.
* @param message The failure message, or null for default
* @param actual The value to test for non-null
*/
fun assertNotNull(message: String?, actual: Any?)
}Interface for providing custom Asserter implementations in a pluggable way.
/**
* Interface for providing Asserter instances. Implementations can be registered
* to contribute custom assertion behavior for specific testing frameworks.
*/
interface AsserterContributor {
/**
* Provides an Asserter instance for the current testing context.
* @return An Asserter implementation, or null if this contributor
* cannot provide one for the current context
*/
fun contribute(): Asserter?
}The global asserter property that holds the current assertion implementation.
/**
* The current Asserter instance used by all assertion functions.
* This can be replaced to integrate with different testing frameworks.
* Defaults to DefaultAsserter if no framework-specific asserter is available.
*/
var asserter: AsserterUsage Examples:
import kotlin.test.*
@Test
fun testCustomAsserterIntegration() {
// Save the original asserter
val originalAsserter = asserter
try {
// Install a custom asserter for specialized behavior
asserter = CustomTestAsserter()
// All assertions now use the custom asserter
assertEquals("expected", "actual") // Uses CustomTestAsserter.assertEquals()
assertTrue(false) // Uses CustomTestAsserter.assertTrue()
} finally {
// Restore original asserter
asserter = originalAsserter
}
}
class CustomTestAsserter : Asserter {
override fun fail(message: String?): Nothing {
throw CustomAssertionError(message ?: "Assertion failed")
}
override fun fail(message: String?, cause: Throwable?): Nothing {
throw CustomAssertionError(message ?: "Assertion failed", cause)
}
override fun assertTrue(message: String?, actual: Boolean) {
if (!actual) {
fail(message ?: "Expected true but was false")
}
}
override fun assertEquals(message: String?, expected: Any?, actual: Any?) {
if (expected != actual) {
fail(message ?: "Expected <$expected> but was <$actual>")
}
}
// ... implement other methods
}The default asserter implementation used when no framework-specific asserter is available.
/**
* Default Asserter implementation that provides basic assertion functionality
* without depending on any specific testing framework.
*/
object DefaultAsserter : Asserter {
/**
* Throws AssertionError with the specified message.
*/
override fun fail(message: String?): Nothing
/**
* Throws AssertionErrorWithCause with the specified message and cause.
*/
override fun fail(message: String?, cause: Throwable?): Nothing
}// Custom JUnit asserter implementation
class JUnitAsserter : Asserter {
override fun fail(message: String?): Nothing {
org.junit.Assert.fail(message)
throw AssertionError() // Never reached
}
override fun assertTrue(message: String?, actual: Boolean) {
org.junit.Assert.assertTrue(message, actual)
}
override fun assertEquals(message: String?, expected: Any?, actual: Any?) {
org.junit.Assert.assertEquals(message, expected, actual)
}
// ... other implementations delegate to JUnit
}
// Register the asserter
class JUnitAsserterContributor : AsserterContributor {
override fun contribute(): Asserter? {
return if (isJUnitAvailable()) JUnitAsserter() else null
}
private fun isJUnitAvailable(): Boolean {
return try {
Class.forName("org.junit.Assert")
true
} catch (e: ClassNotFoundException) {
false
}
}
}// Custom JavaScript asserter for Node.js environments
class NodeJSAsserter : Asserter {
override fun fail(message: String?): Nothing {
js("throw new Error(message || 'Assertion failed')")
}
override fun assertTrue(message: String?, actual: Boolean) {
if (!actual) {
fail(message ?: "Expected true but was false")
}
}
override fun assertEquals(message: String?, expected: Any?, actual: Any?) {
js("""
const assert = require('assert');
assert.deepStrictEqual(actual, expected, message);
""")
}
// ... other implementations
}// Custom native asserter implementation
class NativeAsserter : Asserter {
override fun fail(message: String?): Nothing {
// Native-specific error reporting
platform.posix.fprintf(platform.posix.stderr, "ASSERTION FAILED: %s\n", message ?: "")
platform.posix.exit(1)
throw AssertionError() // Never reached
}
// ... other implementations using native APIs
}import kotlin.test.*
// Step 1: Implement the Asserter interface
class MyFrameworkAsserter : Asserter {
override fun fail(message: String?): Nothing {
MyTestFramework.reportFailure(message ?: "Test failed")
throw MyFrameworkException(message)
}
override fun fail(message: String?, cause: Throwable?): Nothing {
MyTestFramework.reportFailure(message ?: "Test failed", cause)
throw MyFrameworkException(message, cause)
}
override fun assertTrue(message: String?, actual: Boolean) {
if (!actual) {
MyTestFramework.reportBooleanFailure(message, expected = true, actual = false)
fail(message ?: "Expected true but was false")
}
}
override fun assertEquals(message: String?, expected: Any?, actual: Any?) {
if (expected != actual) {
MyTestFramework.reportEqualityFailure(message, expected, actual)
fail(message ?: "Expected <$expected> but was <$actual>")
}
}
// Implement remaining methods...
}
// Step 2: Create an AsserterContributor
class MyFrameworkAsserterContributor : AsserterContributor {
override fun contribute(): Asserter? {
return if (MyTestFramework.isActive()) {
MyFrameworkAsserter()
} else {
null
}
}
}
// Step 3: Register the contributor (usually done during framework initialization)
fun initializeMyFramework() {
val contributor = MyFrameworkAsserterContributor()
val customAsserter = contributor.contribute()
if (customAsserter != null) {
asserter = customAsserter
}
}// Enhanced asserter with additional framework-specific features
class EnhancedFrameworkAsserter : Asserter {
private val testContext = MyFramework.getCurrentTestContext()
override fun fail(message: String?): Nothing {
// Capture additional context
val stackTrace = Thread.currentThread().stackTrace
val testMethod = stackTrace.find { it.methodName.startsWith("test") }
MyFramework.reportDetailedFailure(
message = message,
testClass = testContext.testClass,
testMethod = testMethod?.methodName,
stackTrace = stackTrace
)
throw MyFrameworkException(message)
}
// Override other methods with enhanced reporting...
override fun assertEquals(message: String?, expected: Any?, actual: Any?) {
if (expected != actual) {
// Enhanced diff reporting
val diff = MyFramework.computeDiff(expected, actual)
MyFramework.reportEqualityFailure(
message = message,
expected = expected,
actual = actual,
diff = diff
)
fail(buildEqualityMessage(message, expected, actual, diff))
}
}
private fun buildEqualityMessage(
userMessage: String?,
expected: Any?,
actual: Any?,
diff: String
): String {
val base = userMessage ?: "Assertion failed"
return "$base\nExpected: $expected\nActual: $actual\nDiff:\n$diff"
}
}The framework integration system handles various error scenarios gracefully:
DefaultAsserter if no framework-specific asserter is available@Test
fun testFrameworkFallback() {
// This test works regardless of which testing framework is present
assertEquals("expected", computeValue())
assertTrue(isValid())
// The underlying asserter handles framework-specific reporting
}Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlin--kotlin-test-common