Common test assertions and utilities for Kotlin multiplatform projects
—
Platform-agnostic test annotations that map to appropriate testing frameworks on each Kotlin compilation target. These annotations provide a unified way to mark test functions and test lifecycle methods across JVM, JavaScript, Native, and other Kotlin platforms.
Marks a function as a test case that should be executed by the testing framework.
/**
* Marks a function as a test case.
* The annotated function will be discovered and executed by the testing framework.
* On JVM, maps to JUnit's @Test or TestNG's @Test.
* On JavaScript, integrates with Mocha, Jest, or other JS testing frameworks.
* On Native, works with the native testing infrastructure.
*/
@Target(AnnotationTarget.FUNCTION)
annotation class TestUsage Examples:
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class UserServiceTest {
@Test
fun testCreateUser() {
val userService = UserService()
val user = userService.createUser("Alice", "alice@example.com")
assertEquals("Alice", user.name)
assertEquals("alice@example.com", user.email)
assertTrue(user.id > 0)
}
@Test
fun testValidateEmail() {
val userService = UserService()
assertTrue(userService.isValidEmail("test@example.com"))
assertFalse(userService.isValidEmail("invalid-email"))
assertFalse(userService.isValidEmail(""))
}
@Test
fun testUserAgeValidation() {
val userService = UserService()
// Test valid ages
assertTrue(userService.isValidAge(18))
assertTrue(userService.isValidAge(65))
// Test invalid ages
assertFalse(userService.isValidAge(-1))
assertFalse(userService.isValidAge(0))
assertFalse(userService.isValidAge(150))
}
}Marks a test or test class as ignored, preventing it from being executed.
/**
* Marks a test function or test class as ignored.
* Ignored tests are not executed but are typically reported as skipped.
* Can be applied to individual test functions or entire test classes.
* On JVM, maps to JUnit's @Ignore or TestNG's enabled=false.
* On JavaScript and Native, integrates with platform-specific skip mechanisms.
*/
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class IgnoreUsage Examples:
import kotlin.test.*
class DatabaseTest {
@Test
fun testDatabaseConnection() {
// This test runs normally
val connection = Database.connect()
assertTrue(connection.isConnected())
}
@Test
@Ignore // Temporarily disable this test
fun testSlowDatabaseQuery() {
// This test is skipped during execution
val results = Database.executeSlowQuery()
assertTrue(results.isNotEmpty())
}
@Test
@Ignore // Disable until bug #123 is fixed
fun testBuggyFeature() {
// This test won't run until the @Ignore is removed
val result = BuggyService.processData()
assertNotNull(result)
}
}
// Ignore an entire test class
@Ignore
class ExperimentalFeatureTest {
@Test
fun testExperimentalFeature1() {
// None of the tests in this class will run
}
@Test
fun testExperimentalFeature2() {
// This is also ignored due to class-level annotation
}
}
class PartiallyDisabledTest {
@Test
fun testWorkingFeature() {
// This test runs normally
assertTrue(WorkingService.isAvailable())
}
@Test
@Ignore // Only this specific test is ignored
fun testBrokenFeature() {
// This specific test is skipped
BrokenService.process()
}
}Marks a function to be invoked before each test method in the class.
/**
* Marks a function to be invoked before each test method execution.
* The annotated function is called once before every @Test method in the same class.
* Used for test setup, initialization, and preparing test fixtures.
* On JVM, maps to JUnit's @Before or TestNG's @BeforeMethod.
* On JavaScript and Native, integrates with platform-specific setup mechanisms.
*/
@Target(AnnotationTarget.FUNCTION)
annotation class BeforeTestMarks a function to be invoked after each test method in the class.
/**
* Marks a function to be invoked after each test method execution.
* The annotated function is called once after every @Test method in the same class.
* Used for test cleanup, resource disposal, and restoring test state.
* On JVM, maps to JUnit's @After or TestNG's @AfterMethod.
* On JavaScript and Native, integrates with platform-specific cleanup mechanisms.
*/
@Target(AnnotationTarget.FUNCTION)
annotation class AfterTestCombined Setup/Teardown Examples:
import kotlin.test.*
class FileProcessorTest {
private lateinit var tempFile: File
private lateinit var processor: FileProcessor
@BeforeTest
fun setUp() {
// Called before each individual test method
tempFile = createTempFile("test", ".txt")
processor = FileProcessor()
// Initialize test data
tempFile.writeText("Initial test content")
println("Test setup completed for: ${getCurrentTestMethod()}")
}
@AfterTest
fun tearDown() {
// Called after each individual test method
if (::tempFile.isInitialized && tempFile.exists()) {
tempFile.delete()
}
processor.cleanup()
println("Test cleanup completed for: ${getCurrentTestMethod()}")
}
@Test
fun testFileRead() {
// setUp() called automatically before this
val content = processor.readFile(tempFile)
assertEquals("Initial test content", content)
// tearDown() called automatically after this
}
@Test
fun testFileWrite() {
// setUp() called automatically before this (fresh tempFile)
processor.writeFile(tempFile, "New content")
val content = tempFile.readText()
assertEquals("New content", content)
// tearDown() called automatically after this
}
@Test
fun testFileAppend() {
// setUp() called automatically before this (fresh tempFile)
processor.appendToFile(tempFile, " - appended")
val content = tempFile.readText()
assertEquals("Initial test content - appended", content)
// tearDown() called automatically after this
}
}
class DatabaseConnectionTest {
private lateinit var connection: DatabaseConnection
private lateinit var testDatabase: String
@BeforeTest
fun initializeDatabase() {
// Create a fresh test database for each test
testDatabase = "test_db_${System.currentTimeMillis()}"
connection = DatabaseConnection.create(testDatabase)
connection.connect()
// Set up test schema
connection.execute("CREATE TABLE users (id INT, name VARCHAR(100))")
connection.execute("INSERT INTO users VALUES (1, 'Test User')")
}
@AfterTest
fun cleanupDatabase() {
// Clean up after each test
if (::connection.isInitialized && connection.isConnected()) {
connection.execute("DROP TABLE IF EXISTS users")
connection.disconnect()
}
// Remove test database
DatabaseUtils.dropDatabase(testDatabase)
}
@Test
fun testSelectUser() {
val users = connection.query("SELECT * FROM users")
assertEquals(1, users.size)
assertEquals("Test User", users[0]["name"])
}
@Test
fun testInsertUser() {
connection.execute("INSERT INTO users VALUES (2, 'Another User')")
val users = connection.query("SELECT * FROM users")
assertEquals(2, users.size)
}
@Test
@Ignore // Database is slow in CI environment
fun testComplexQuery() {
// This test is ignored but would still get setUp/tearDown if it ran
val result = connection.query("SELECT COUNT(*) FROM users WHERE name LIKE '%Test%'")
assertEquals(1, result[0]["count"])
}
}The test annotations follow a predictable execution order:
class TestLifecycleExample {
init {
println("1. Test class constructor called")
}
@BeforeTest
fun setup() {
println("2. @BeforeTest: Setting up for test")
}
@Test
fun testA() {
println("3. @Test: Running testA")
}
@Test
fun testB() {
println("3. @Test: Running testB")
}
@AfterTest
fun cleanup() {
println("4. @AfterTest: Cleaning up after test")
}
}
// Output would be:
// 1. Test class constructor called
// 2. @BeforeTest: Setting up for test
// 3. @Test: Running testA
// 4. @AfterTest: Cleaning up after test
// 2. @BeforeTest: Setting up for test
// 3. @Test: Running testB
// 4. @AfterTest: Cleaning up after test@Test or TestNG @Test@Ignore or TestNG enabled=false@Before/@BeforeEach or TestNG @BeforeMethod@After/@AfterEach or TestNG @AfterMethodit(), Jest test(), or other JS testing frameworksit.skip(), test.skip())beforeEach()/afterEach() hooksAlways pair resource acquisition with proper cleanup:
@BeforeTest
fun setup() {
// Acquire resources
database = TestDatabase.create()
httpServer = TestServer.start(port = 8080)
}
@AfterTest
fun cleanup() {
// Always clean up, even if test fails
database?.close()
httpServer?.stop()
}Use meaningful comments when ignoring tests:
@Test
@Ignore // TODO: Re-enable after fixing issue #456 - NullPointerException in edge case
fun testEdgeCase() {
// Test implementation
}Ensure tests don't depend on execution order:
@BeforeTest
fun resetState() {
// Reset to known state before each test
GlobalState.reset()
TestData.clear()
}Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlin--kotlin-test-common