Test compositions
Test real response logic by replacing its dependency Tiles. mosaic-test uses the same runtime as production, with a test dispatcher and selected substitutions. Use Canvas sources for request input and service fakes.
Quick Start
Section titled “Quick Start”Installation
Section titled “Installation”In an existing Kotlin/JVM project with a configured test runner:
dependencies { testImplementation("org.buildmosaic:mosaic-test:0.6.0") testImplementation(kotlin("test"))}Mosaic targets JVM 17 and uses Kotlin 2.4.20 with language/API level 2.4. Runtime artifacts are tested with Kotlin 2.3.0 consumers and use stdlib/kotlin-test 2.4.20 and coroutines core/test 1.11.0. See runtime compatibility.
mosaic-test exposes Mosaic core and coroutine test APIs. If you use the optional BOM, omit the Mosaic dependency version.
Your first Tile test
Section titled “Your first Tile test”Inside runTest, mosaicBuilder() uses the enclosing TestScope and its scheduler. This complete test replaces a dependency while leaving the response Tile’s formatting logic real:
import kotlinx.coroutines.test.runTestimport org.buildmosaic.core.singleTileimport org.buildmosaic.test.mosaicBuilderimport kotlin.test.Test
class GreetingTest { @Test fun `greeting formats the dependency result`() = runTest { val nameTile = singleTile { "Production name" } val greetingTile = singleTile { "Hello, ${compose(nameTile)}!" }
val testMosaic = mosaicBuilder() .withMockTile(nameTile, "Jane") .build()
testMosaic.assertEquals(greetingTile, "Hello, Jane!") }}Mock the same Tile instance that the subject composes. Unmocked tiles execute their real blocks. Avoid mocking the subject when you want to verify its logic. Place the following methods in your test class, using these same imports plus any shown locally.
Provide Canvas input
Section titled “Provide Canvas input”Use withCanvasSource to exercise actual source lookups. No production Canvas construction is needed for this test:
import org.buildmosaic.core.injection.CanvasKeyimport org.buildmosaic.core.source
@Testfun `greeting reads request input`() = runTest { val userIdKey = CanvasKey(String::class, "userId") val greetingTile = singleTile { "Hello, ${source(userIdKey)}!" }
val testMosaic = mosaicBuilder() .withCanvasSource(userIdKey, "user-123") .build()
testMosaic.assertEquals(greetingTile, "Hello, user-123!")}You can also register a service fake by class with withCanvasSource(Service::class, fakeService) or by a qualified key. Only provide the inputs used by real blocks; replacing a Tile bypasses its own Canvas reads.
Mock MultiTile results
Section titled “Mock MultiTile results”Supply a result map for requested keys, then test the consumer’s calculation:
import org.buildmosaic.core.multiTile
@Testfun `total sums mocked prices for requested SKUs`() = runTest { val pricingTile = multiTile<String, Int> { error("External service") } val totalTile = singleTile { compose(pricingTile, listOf("SKU1", "SKU2")).values.sum() }
val testMosaic = mosaicBuilder() .withMockTile(pricingTile, mapOf("SKU1" to 100, "SKU2" to 250)) .build()
testMosaic.assertEquals(totalTile, 350)}This verifies composition with mocked prices, not batching efficiency. To verify a Tile’s batching behavior, leave that MultiTile real, provide a recording service fake through Canvas, and assert which keys reach the service. See the checked-in ProductsByIdTile tests for examples of exercising the real MultiTile.
Verify exception propagation
Section titled “Verify exception propagation”@Testfun `greeting propagates a dependency failure`() = runTest { val nameTile = singleTile { "Production name" } val greetingTile = singleTile { "Hello, ${compose(nameTile)}!" }
val testMosaic = mosaicBuilder() .withFailedTile(nameTile, IllegalStateException("Service unavailable")) .build()
testMosaic.assertThrows(greetingTile, IllegalStateException::class)}This asserts propagation, not graceful recovery. If your Tile catches a specific failure and returns a fallback, assert that fallback result in a separate test.
Simulate delays with virtual time
Section titled “Simulate delays with virtual time”withDelayedTile uses coroutine delay on the test scheduler. runTest advances virtual time while awaiting the result; elapsed wall-clock time is not the assertion target. This checks simulated timing behavior, not performance:
import kotlinx.coroutines.ExperimentalCoroutinesApiimport kotlinx.coroutines.test.currentTimeimport kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)@Testfun `delayed mock advances virtual time before returning`() = runTest { val dataTile = singleTile { "Production data" } val testMosaic = mosaicBuilder() .withDelayedTile(dataTile, "Test data", delayMs = 200) .build()
val start = currentTime testMosaic.assertEquals(dataTile, "Test data") assertEquals(200L, currentTime - start)}Mock and assertion reference
Section titled “Mock and assertion reference”All mock behaviors support both Tile and MultiTile dependencies:
| Builder method | Behavior |
|---|---|
withMockTile(tile, response) |
Return a value, or a map for a MultiTile |
withFailedTile(tile, throwable) |
Throw when composed |
withDelayedTile(tile, response, delayMs) |
Delay in virtual time, then return |
withCustomTile(tile) { ... } |
Execute a suspending provider with a Mosaic receiver; MultiTile providers also receive a set of keys |
withCanvasSource(key, value) |
Make input or a service fake available to source |
A custom provider can use source and compose dependencies. For a MultiTile, return a map containing the requested keys. Build a fresh test Mosaic for each scenario so caches and mocks do not leak between tests.
| Assertion | What it verifies |
|---|---|
assertEquals(tile, expected) |
Tile result equality |
assertEquals(tile, keys, expectedMap) |
MultiTile result equality for the requested keys |
assertThrows(tile, ExceptionType::class) |
Tile throws the expected exception type |
assertThrows(multiTile, listOf(key), ExceptionType::class) |
MultiTile throws for the requested keys |
Use testMosaic.compose(...) with ordinary Kotlin test assertions for custom checks. See the runnable order example tests for response composition, request inputs, and real Tile behavior.