How do I mock dependencies in EK9 tests without Mockito?
← Testing · Ref: Q808
EK9 does not need Mockito, unittest.mock, or any mocking library. Test doubles are ordinary implementations of a contract, chosen either by the caller or by dependency injection.
THE PATTERN
1. Define the behaviour as an abstract contract
2. Production code depends on the contract, not the concrete implementation
3. In tests, provide a test implementation of the contract
4. For injected dependencies, an application block selects which implementation is registered
WHY NO MOCKING FRAMEWORK
Mocking frameworks exist because languages allow tight coupling to concrete classes. In EK9:
- Code depends on traits or abstract components, not concrete types
- The DI system wires implementations at compile time
- Test implementations are just another implementation of the contract
- No reflection, no bytecode manipulation, no proxy objects
TRAIT-BASED TEST DOUBLES (passed directly)
A class implements a trait with 'with trait of':
defines trait Formatter format() as pure abstract -> number as Integer <- rtn as String?
defines class SimpleFormatter with trait of Formatter override format() as pure -> number as Integer <- rtn as String: $number
Any code that takes a Formatter can be handed the test double.
INJECTED TEST DOUBLES (components)
Only components can be registered and injected - registering a class, or registering anything 'as' a trait, is E50020. So the injected contract is an abstract component, the test double is wired in an application, and the program runs with that application:
defines component DataStore as abstract fetch() as abstract -> key as String <- rtn as String?
default operator ?
FakeDataStore extends DataStore override fetch() -> key as String <- rtn as String: "test-value"
default operator ?
defines application TestApp register FakeDataStore() as DataStore
defines program MockingDemo() with application of TestApp stdout <- Stdout() store as DataStore! stdout.println(store.fetch("user"))
The program prints test-value. A ProductionApp registers RealDataStore() as DataStore instead; the program code does not change.
ADVANTAGES
- Compile-time verified: missing method implementations caught immediately
- No magic: test doubles are normal classes and components
- Reusable: same test double works across all tests
- Type-safe: cannot accidentally mock the wrong method signature
See Q227 for DI basics. See Q324 for DI deep dive. See Q155 for basic testing.
Example
defines module qa.testdeep.mocking defines trait <?- The trait defines the contract. Production and test code both implement this. -?> Formatter format() as pure abstract -> number as Integer <- rtn as String? defines class // === PRODUCTION IMPLEMENTATION === FancyFormatter with trait of Formatter override format() as pure -> number as Integer <- rtn as String: `[${number}]` // === TEST DOUBLE === SimpleFormatter with trait of Formatter override format() as pure -> number as Integer <- rtn as String: $number defines component // === INJECTED CONTRACT: an abstract component, not a trait === DataStore as abstract fetch() as abstract -> key as String <- rtn as String? default operator ? RealDataStore extends DataStore override fetch() -> key as String <- rtn as String: `db-value-for-${key}` default operator ? FakeDataStore extends DataStore override fetch() -> key as String <- rtn as String: "test-value" default operator ? defines application // Production wiring ProductionApp register RealDataStore() as DataStore // Test wiring: the fake replaces the real store TestApp register FakeDataStore() as DataStore defines program MockingDemo() with application of TestApp stdout <- Stdout() // Trait doubles: pass whichever implementation you want fancy <- FancyFormatter() stdout.println(fancy.format(42)) simple <- SimpleFormatter() stdout.println(simple.format(42)) // DI doubles: TestApp injects FakeDataStore store as DataStore! stdout.println(store.fetch("user")) stdout.println("No Mockito needed") stdout.println("Traits and components replace mocking frameworks")
Common mistakes
E50020 — A class implements a trait with 'with trait of', not 'extends' — using 'extends' on a trait is an incompatible genus (class vs trait). See ek9 -h E50020 for details.
Incorrect:
FancyFormatter extends Formatter
Correct:
FancyFormatter with trait of Formatter
Other ways to ask this
- What is the EK9 equivalent of Mockito?
- How do I use traits for test doubles in EK9?
- How do I test components with injected dependencies in EK9?
Coming from another language?
Java: Mockito mock()/when()/verify(), Spring @MockBean. Python: unittest.mock.patch(), MagicMock. Rust: mockall crate with #[automock]. Go: interfaces + hand-written fakes (standard pattern). Kotlin: MockK or Mockito-kotlin. EK9: trait implementations as test doubles passed directly, or abstract components with an application that registers the fake - no mocking library needed.
Keywords: double, fake, injection, application, test, component, stub, trait, interface, mock, dependency, mockito