The mock implementations of the interfaces can now also be implemented. These mock classes can be as simple or as complex as you desire, depending on what behavior you need to emulate and what it is you're asserting. Note that you do not have to fully implement all methods; stubs are fine. Implement what is needed for your test scenarios:
private class MockDashboard implements IDashboard { public Boolean initialiseCalled = false; public Boolean updateRPMsCalled = false; public Integer updateRPMsCalledWith = null; public void initialise() { initialiseCalled = true; } public void updateRPMs(Integer rpms) { updateRPMsCalled = true; updateRPMsCalledWith = rpms; } public void off() { } } private class MockEngine implements IEngine { public Boolean startCalled = false; public void start() { startCalled = true; } public void stop() { } public Boolean isRunning() { return true; } } private class MockDisplay implements IDisplay { public Boolean showMessageCalled = false; public String showMessageCalledWithMessage = null; public void backlight(Boolean onOff) { } public void showMessage(
Integer positionX, Integer positionY, String message) { showMessageCalled = true; showMessageCalledWithMessage = message; } public String getMessageShowAt(
Integer positionX, Integer positionY) { return null; } public Boolean isVisible() { return false; } }
After making the preceding changes and the introduction of the mocking classes, the implementation of the Car class object model now supports unit testing through CDI.
Each of the following unit tests resides in the corresponding test class within the sample code for this chapter, for example, CarTest, EngingeTest, and DashboardTest.
The following is a unit test for the Car.start method:
@IsTest private static void whenCarStartCalledDashboardAndEngineInitialised () { // Given MockDashboard mockDashboard = new MockDashboard(); MockEngine mockEngine = new MockEngine(); // When Car car = new Car(mockEngine, mockDashboard); car.start(); // Then System.assert(car.isRunning()); System.assert(mockDashboard.initialiseCalled); System.assert(mockEngine.startCalled); }
Notice that the test did not need any setup to fulfill the setup requirements of the engine and dashboard dependencies, such as constructing an instance of the Display class.
Mock classes can also record the values passed as parameters for assertion later. The following is a unit test for the engine.start method:
@IsTest private static void whenStartCalledDashboardUpdated() { // Given MockDashboard mockDashboard = new MockDashboard(); // When Engine engine = new Engine(mockDashboard); engine.start(); // Then System.assert(engine.isRunning()); System.assert(mockDashboard.updateRPMsCalled); System.assertEquals(1000,
mockDashboard.updateRPMsCalledWith); }
It uses the mock implementation of the dashboard to confirm that the Dashboard.updateRPMs method was correctly passed the corresponding value. The following unit test is for the Dashboard.updateRPMs method:
@IsTest private static void whenUpdateRPMsCalledMessageIsDisplayed() { // Given MockDisplay mockDisplay = new MockDisplay(); // When Dashboard dashboard = new Dashboard(mockDisplay); dashboard.updateRPMs(5000); // Then System.assert(mockDisplay.showMessageCalled); System.assertEquals('RPM:5000',
mockDisplay.showMessageCalledWithMessage); }
The preceding code uses a mock implementation of the Display class to test the correct interaction between the Dashboard and the Display class. The interaction being tested here is that the display text is correctly calculated and passed to the Display class when the updateRPMs method is called on the Dashboard class.
Using interfaces to achieve dependency injection has an overhead in the development and use of interfaces in the production code. This is something Salesforce has improved on with the introduction of the Apex Stub API, described later in this chapter. As the Stub API is still quite new, you may still see the DI accomplished with interfaces. The Apex Enterprise Patterns library utilized it prior to the arrival of the Stub API. Also keep in mind, as we have seen in previous sections of this book, that Apex interfaces also have many other uses in providing flexibility in your architecture's evolution and reducing coupling between clearly defined boundaries, such as those in this book and those your logic can also define.