An application and its test double

If you know Spring or Guice you expect annotations and a scan; EK9 has neither. A component is a construct, and an Application is a construct that lists which implementation stands for which contract. A program names the Application it runs with and asks for what it needs with a ! suffix. This lesson builds all of it with the IDE's tools: a Notifier contract, one implementation that prints and one that records, an Application for each - and then runs the first and tests with the second. Both files are just a module line so far. To play it in the IDE: Help > Lessons > Applications and services…, or ek9 -ide -lesson application-and-di.

The EK9 IDE as the lesson opens

Start from

The code the lesson starts from: copy it into a file of your own and follow the steps.

notify.ek9

#!ek9
defines module lessons.notify

dev/tests.ek9

#!ek9
defines module lessons.notify

Step 1: Notifier — the contract

Choose Code > New Construct > Component…

The EK9 IDE once this is done: Choose Code > New Construct > Component…

Notifier is the contract: what any notifier can do, with nothing behind it. Type Notifier, choose Abstract under Extensibility, then Preview.

The EK9 IDE just before choosing Preview: Notifier is the contract

The preview shows Notifier about to be written, as abstract. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows Notifier about to be written, as abstract

Right-click the Notifier line and choose Add Method…

The EK9 IDE once this is done: Right-click the Notifier line and choose Add Method…

deliver is the one thing a Notifier does: it takes a message and answers with a String. Tick abstract - the contract only names it - add one parameter, message of type String, and choose Apply.

The EK9 IDE just before choosing Apply: deliver is the one thing a Notifier does

Step 2: Two implementations

Choose Code > New Construct > Component…

The EK9 IDE once this is done: Choose Code > New Construct > Component…

ConsoleNotifier is the one that really notifies: it answers with the message marked as sent. Type ConsoleNotifier, put Notifier in Extends, and choose Preview.

The EK9 IDE just before choosing Preview: ConsoleNotifier is the one that really notifies

The preview shows ConsoleNotifier is Notifier - and, because Notifier owes deliver(), its override stub comes with it. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows ConsoleNotifier is Notifier - and, because Notifier owes deliver(), its override stub comes with it

Select the stub, from <- rtn as String? down to its //AI line, and type ConsoleNotifier's answer. Saves when typed.

The EK9 IDE once this is done: Select the stub, from <- rtn as String? down to its //AI line, and type ConsoleNotifier's answer

Choose Code > New Construct > Component…

The EK9 IDE once this is done: Choose Code > New Construct > Component…

RecordingNotifier is the test double: it answers with the message marked as recorded, and does nothing else. Type RecordingNotifier, put Notifier in Extends, and choose Preview.

The EK9 IDE just before choosing Preview: RecordingNotifier is the test double

The preview shows RecordingNotifier is Notifier - and, because Notifier owes deliver(), its override stub comes with it. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows RecordingNotifier is Notifier - and, because Notifier owes deliver(), its override stub comes with it

Select the stub, from <- rtn as String? down to its //AI line, and type RecordingNotifier's answer. Saves when typed.

The EK9 IDE once this is done: Select the stub, from <- rtn as String? down to its //AI line, and type RecordingNotifier's answer

Step 3: Two applications

Choose Code > New Construct > Application…

The EK9 IDE once this is done: Choose Code > New Construct > Application…

ShopApp is production wiring: wherever a Notifier is asked for, the ConsoleNotifier answers. Type ShopApp, add one registration - Component ConsoleNotifier, As Interface Notifier - and choose Preview.

The EK9 IDE just before choosing Preview: ShopApp is production wiring

The preview shows ShopApp with its one register line: register ConsoleNotifier() as Notifier. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows ShopApp with its one register line

Choose Code > New Construct > Application…

The EK9 IDE once this is done: Choose Code > New Construct > Application…

TestApp is test substitution: the SAME contract, wired to the double. There is no mocking framework - a second Application is all it takes. Type TestApp, add one registration - Component RecordingNotifier, As Interface Notifier - and choose Preview.

The EK9 IDE just before choosing Preview: TestApp is test substitution

The preview shows TestApp with its one register line: register RecordingNotifier() as Notifier. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows TestApp with its one register line

Step 4: A program that injects

Choose Code > New Construct > Program…

The EK9 IDE once this is done: Choose Code > New Construct > Program…

Announce is the program's name. Type ShopApp into the application field - 'with application of ShopApp' - and add one injection, notifier of type Notifier: the ! line asks for a Notifier, never a ConsoleNotifier. Choose Preview.

The EK9 IDE just before choosing Preview: Announce is the program's name

The preview shows Announce about to be written, bound to ShopApp, with notifier as Notifier! beneath it. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows Announce about to be written, bound to ShopApp, with notifier as Notifier! beneath it

Select the stub, from notifierInUse <- notifier down to its //AI line, and type the body: a Stdout, and a line printing what the notifier delivers. Saves when typed.

The EK9 IDE once this is done: Select the stub, from notifierInUse <- notifier down to its //AI line, and type the body

Step 5: Run it

Choose Run > Run Program…

The EK9 IDE once this is done: Choose Run > Run Program…

The project's only program is already chosen. Choose Run.

sent: order shipped - the program asked for a Notifier and the application it is bound to supplied the ConsoleNotifier. Choose Close.

The EK9 IDE just before choosing Close: sent

Step 6: DI Wiring

Choose Analyse > DI Wiring.

The EK9 IDE once this is done: Choose Analyse > DI Wiring

Both applications are drawn: ShopApp registers ConsoleNotifier as Notifier, TestApp registers RecordingNotifier as Notifier, and the injection site Announce.notifier, which needs a Notifier, is ticked under each. The border is HEALTHY because every site has a registration that supplies it - a missing one is a compile error, not a runtime surprise. Click any row to open its source.

The EK9 IDE once this is done: Both applications are drawn

Step 7: A test with the double

Open dev/tests.ek9 - it holds only its module line. A test goes in dev/, and a test in the same module needs no references block.

The EK9 IDE once this is done: Open dev/tests.ek9 - it holds only its module line

Choose Code > New Construct > Test…

The EK9 IDE once this is done: Choose Code > New Construct > Test…

NotifierTest is the test's name. Bind it to TestApp, not ShopApp - the test gets the double - and inject notifier of type Notifier exactly as Announce did. Choose Preview.

The EK9 IDE just before choosing Preview: NotifierTest is the test's name

The preview shows NotifierTest about to be written, marked @Test and bound to TestApp. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows NotifierTest about to be written, marked @Test and bound to TestApp

Select the stub, from notifierInUse <- notifier down to its //AI line, and type the claim: the notifier delivers "order shipped" and answers with the recorded form. Saves when typed.

The EK9 IDE once this is done: Select the stub, from notifierInUse <- notifier down to its //AI line, and type the claim

Run ▸ Run Tests (⌘T) runs the test with TestApp's wiring. Choose Run Tests.

The EK9 IDE once this is done: Run ▸ Run Tests (⌘T) runs the test with TestApp's wiring

The Tests tab shows NotifierTest passing: the same Notifier contract, answered by the RecordingNotifier because the test is bound to TestApp - while Announce, bound to ShopApp, still gets the ConsoleNotifier.

The EK9 IDE once this is done: The Tests tab shows NotifierTest passing

The result

notify.ek9 now holds a Notifier contract, the ConsoleNotifier that really prints and the RecordingNotifier that stands in for it, two applications that wire one each, and Announce, which asks for a Notifier and is told which by the application it is bound to; dev/tests.ek9 holds a @Test bound to the other. Nothing was scanned, annotated or mocked: wiring is a construct the compiler checks, and substitution is a second Application. The tools - ek9_new_construct, ek9_add_method, ek9_add_application and ek9_add_program (isTest for the test) - are what an AI assistant calls to do the same.

Where this lesson comes from