Introduction to unit testing with Apex

At this stage, you're probably wondering just how it is technically possible, using code, to substitute or inject (to use the correct term) different compiled code during test execution. To illustrate the various options for dependency injection, let's start with a simple code example. We will explore how unit testing can be applied to Apex Enterprise Patterns later in this chapter.

The following diagram shows the Unified Modeling Language (UML) for a Car class model, which has been designed with SOC in mind. Responsibilities such as the engine, dashboard, and the digital readout display have been separated. This is a pure Apex code example to illustrate how dependencies between classes can be managed with dependency injection (DI):

The following code is for the Car class. It has a dependency on methods from the Dashboard and Engine classes. The caller must set up and provide instances of these classes for the methods to function correctly:

public class Car { 
     
    private Engine engine; 
    private Dashboard dashboard; 
    private Boolean isRunning; 
     
    public Car(Engine engine, Dashboard dashboard) { 
        this.dashboard = dashboard; 
        this.engine = engine; 
    } 
     
    public void start() { 
        dashboard.initialise(); 
        engine.start(); 
    } 
     
    public void stop() { 
        engine.stop();         
        dashboard.off(); 
    } 
     
    public Boolean isRunning() { 
        return engine.isRunning(); 
    } 
} 

The start and stop methods contain functionality that ensures that the dashboard and engine are initialized accordingly. The following code for the Dashboard class requires an instance of the Display class, which handles the display of information:

public class Dashboard { 
     
    private Display display; 
     
    public Dashboard(Display display) { 
        this.display = display; 
    } 
     
    public void initialise() { 
        display.backlight(true); 
        display.showMessage(10, 20, 'Hello Driver!');         
    } 
     
    public void updateRPMs(Integer rpms) { 
        display.showMessage(10,10, 'RPM:' + rpms); 
    } 
     
    public void off() {         
        display.backlight(false);         
    } 
} 

The methods on the Dashboard class integrate with the Display class to show messages on the digital display of the car at various locations. The Display class has no dependencies, so, for simplicity, is not shown here. (If you want to view its code, you can refer to the sample code for this chapter.) Finally, the Engine class is shown as follows:

public class Engine { 
 
    private Dashboard dashboard; 
    private Boolean isRunning; 
 
    public Engine(Dashboard dashboard) { 
        this.dashboard = dashboard;      
    }    
          
    public void start() {         
        dashboard.updateRPMs(1000); 
        isRunning = true; 
    }    
     
    public void stop() { 
        dashboard.updateRPMs(0); 
        isRunning = false; 
    }  
     
    public Boolean isRunning() { 
        return isRunning; 
    } 
} 

The methods in the Engine class call methods on the Dashboard class to update the driver on the revolutions per minute (RPM) of the engine.

This is sample code. Real code would likely include classes representing various hardware sensors.

First, let's take a look at what an integration test looks like for Car:

@IsTest 
private class CarTest { 
     
    @IsTest 
    private static void integrationTestStartCar() { 
         
        // Given 
        Display display = new Display(); 
        Dashboard dashboard = new Dashboard(display); 
        Engine engine = new Engine(dashboard); 
 
        // When 
        Car car = new Car(engine, dashboard); 
        car.start(); 
         
        // Then 
        System.assertEquals(true, car.isRunning()); 
        System.assertEquals(true, engine.isRunning()); 
        System.assertEquals(true, display.isVisible()); 
        System.assertEquals('Hello Driver!',
display.getMessageShowAt(10,20));
System.assertEquals('RPM:1000',
display.getMessageShowAt(10,10)); } }

The setup for this test looks simple, but imagine if each of the classes constructed required other configuration and data to be created—the setup logic could grow quickly. This integration test is perfectly valid and useful in its own right. However, as I mentioned earlier, you cannot assume just because you have fully unit tested methods that everything still works when you put it all together!

The preceding test code is split into three main sections: Given, When, and Then. This type of approach and thinking originates from another practice, known as behavior-driven development (BDD). You can read more about this on Martin Fowler's website at https://martinfowler.com/bliki/GivenWhenThen.html.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.55.14