How do I mock dependencies in EK9 tests without Mockito?

← Testing · Ref: Q808

EK9 does not need Mockito, unittest.mock, or any mocking library. Instead, you use traits and dependency injection to create test doubles at the language level.

THE PATTERN

1. Define behaviour as a trait (interface)
2. Production code depends on the trait, not the concrete implementation
3. In tests, provide a test implementation of the trait
4. DI wiring in the application block selects which implementation to use

WHY NO MOCKING FRAMEWORK

Mocking frameworks exist because languages allow tight coupling to concrete classes. EK9 prevents this:
- Components depend on traits (interfaces), not concrete types
- The DI system wires implementations at compile time
- Test implementations are just another implementation of the trait
- No reflection, no bytecode manipulation, no proxy objects

TRAIT-BASED TEST DOUBLES

Define a trait:

  DataStore as trait
    fetch() as pure abstract
      -> key as String
      <- rtn as String?

Production implementation:

  RealDataStore is DataStore
    override fetch() as pure
      -> key as String
      <- rtn as String: lookupDatabase(key)

Test implementation:

  FakeDataStore is DataStore
    override fetch() as pure
      -> key as String
      <- rtn as String: "test-value"

TEST APPLICATION

Wire the test double in a test application block:

  defines application
    TestApp
      register FakeDataStore() as DataStore

ADVANTAGES

- Compile-time verified: missing method implementations caught immediately
- No magic: test doubles are normal classes implementing traits
- 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 program

    MockingDemo()
      stdout <- Stdout()

      // Production: uses FancyFormatter
      fancy <- FancyFormatter()
      stdout.println(fancy.format(42))

      // Test: uses SimpleFormatter (the "mock")
      simple <- SimpleFormatter()
      stdout.println(simple.format(42))

      // Both implement Formatter trait
      // DI wiring chooses which one:
      //   Production app: register FancyFormatter() as Formatter
      //   Test app:       register SimpleFormatter() as Formatter

      stdout.println("No Mockito needed")
      stdout.println("Traits 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, DI wiring selects implementation, no mocking library needed.

Keywords: double, mock, stub, interface, trait, dependency, test, fake, injection, mockito