The Promise
From a ViewModel with 12 constructor parameters — down to one.
Before
class InitialViewModel(
private val applicationMetadata: ApplicationMetadata,
private val retrieveAndStoreTogglesUseCase: RetrieveAndStoreTogglesUseCase,
watchTogglesUseCase: WatchTogglesUseCase,
private val persistCachedInfoUseCase: PersistCachedInfoUseCase,
private val fetchActiveUserAndStoreUseCase: FetchActiveUserAndStoreUseCase,
fetchPreferredLocaleUseCase: FetchPreferredLocaleUseCase,
fetchVisualThemeUseCase: FetchVisualThemeUseCase,
private val metricsReporterPort: MetricsReporterPort,
val runtimeSettings: RuntimeSettings,
val speedMonitor: SpeedMonitor,
val cloudPerformanceTracker: PerformanceTraceListener,
val simpleLoggingTracker: SimpleLoggingTracker,
) : ViewModel()
After
class InitialViewModel(
private val mediator: Mediator,
) : ViewModel()
Every action becomes mediator.send(...). Each use-case moves into a focused handler — testable in isolation, no mocking library needed.
Hello, Mocking 👋
No mocking library. No 20-line setup. Just a plain fake handler.
Before — Mockk hell
@Test
fun `place order - notifies user on success`() {
val notificationService = mockk<NotificationService>()
val inventoryRepo = mockk<InventoryRepository>()
val orderRepo = mockk<OrderRepository>()
val paymentGateway = mockk<PaymentGateway>()
val emailSender = mockk<EmailSender>()
// … 8 more mocks …
every { inventoryRepo.reserve(any()) } returns true
every { orderRepo.save(any()) } returns order
every { paymentGateway.charge(any()) } returns receipt
every { notificationService.notify(any()) } just Runs
// … 12 more stubs …
val vm = OrderViewModel(
notificationService, inventoryRepo,
orderRepo, paymentGateway, emailSender, …
)
vm.placeOrder(cart)
verify { notificationService.notify(match { it.type == "ORDER_PLACED" }) }
}
After — Just a fake
@Test
fun `place order - notifies user on success`() {
val fakeNotify = FakePlaceOrderHandler(shouldNotify = true)
val mediator = TestMediator(fakeNotify)
val vm = OrderViewModel(mediator)
vm.placeOrder(cart)
assertTrue(fakeNotify.notified)
}
// A simple fake — no mocking library needed
class FakePlaceOrderHandler(val shouldNotify: Boolean)
: RequestHandler<PlaceOrderCommand, OrderResult> {
var notified = false
override suspend fun handle(...): OrderResult {
if (shouldNotify) notified = true
return OrderResult.Success
}
}
Each handler is a pure function. Swap it with a fake object in tests — no mocking framework, no every/verify incantations.
Quick Example
Three steps: define a request, implement a handler, send it.
// 1. Define a request
data class GetUserQuery(val id: String) : Request<User>
// 2. Implement a handler
class GetUserHandler(private val db: UserRepository)
: RequestHandler<GetUserQuery, User> {
override suspend fun handle(
mediator: Mediator,
requestContext: RequestContext,
request: GetUserQuery,
): User = db.findById(request.id) ?: error("Not found")
}
// 3. Wire it up & use it
val mediator = MediatorFactory.create(
registrars = listOf(AppRegistrar(db)),
)
val user: User = mediator.send(GetUserQuery("user-1"))
◀

Why MediatorK?
⚡
Coroutine-native
suspend all the way down — no callbacks, no blocking wrappers.
🧩
KMP ready
Single commonMain dependency. Works on JVM, Android, iOS, and more.
🔌
Framework-agnostic
Spring Boot, Ktor, Koin, or plain Kotlin — all first-class.
🪶
Zero magic
No kotlin-reflect, no annotation processors, no code generation.
🧪
Testable by design
ViewModel tests need zero mocking — swap real handlers for fakes.
🛡️
Pipeline behaviors
Compose cross-cutting concerns: logging, retry, auth, circuit-breaker.
Supported Platforms
All APIs live in commonMain — one dependency, every target.
JVM
Spring Boot · Ktor · CLI
Android
androidTarget · native
iOS
iosArm64 · iosSimulatorArm64 · iosX64
macOS
macosArm64 · macosX64
tvOS
tvosArm64 · tvosSimulatorArm64 · tvosX64
watchOS
watchosArm32/64 · watchosSimulatorArm64
Linux
linuxArm64 · linuxX64
Web / Wasm
js · wasmJs · wasmWasi
Ready to build?
Add MediatorK to your project in under a minute.
