Module 1: Mocking 1

Module Overview

Learn about mocking fundamentals and how to use them in your tests to create isolated and effective unit tests.

Introduction to Mocking

Mocking is essential for writing effective unit tests. It allows you to test classes in isolation by creating dummy implementations of their dependencies. This ensures your tests are deterministic, fast, and focused on the specific code you're testing.

Mock objects let you define the output of method calls from dependencies, giving you precise control over test conditions without relying on external systems or real implementation details.

// Example of creating a mock
@Mock
private RenterDao renterDao;

@Mock
private DriverLicenseService driverLicenseService;

@InjectMocks
private RenterRegistrar renterRegistrar;

@BeforeEach
public void setup() {
    initMocks(this);
}

// Defining mock behavior
when(renterDao.getRegisteredRenter(driverLicenseId)).thenReturn(driver);

Learning Objectives

  • Understand the purpose and benefits of mocking in unit tests
  • Learn to use Mockito to create mock objects
  • Master the basics of stubbing method responses
  • Practice verifying mock interactions
  • Implement mocks to isolate code units for testing
  • Implement unit tests that isolate code by mocking dependencies
  • Design unit tests that verify class interactions with dependencies
  • Create unit tests that verify exception handling from dependencies
  • Design unit tests for expected error conditions and exceptions
  • Refactor classes to make them more testable without direct dependency instantiation
  • Discuss the advantages of dependency injection for testing

When to Use Mocking

Mocking provides several key benefits in unit testing:

  • Deterministic tests: Tests produce predictable results regardless of external factors
  • Testing edge cases: Easily test rare conditions that are difficult to reproduce naturally
  • Speed: Tests run faster without waiting for real dependencies like databases or networks
  • Cost efficiency: Avoid expenses associated with real services or resources
  • Isolation: Test only your specific code unit without interference from dependencies
// Example of mocking exceptional behavior
@Test
public void getRegisteredRenter_unregisteredDriver_throwsNotRegisteredException() throws NotRegisteredException {
    // GIVEN
    String driverLicenseId = "ABC1234567";
    when(renterDao.getRegisteredRenter(driverLicenseId)).thenThrow(NotRegisteredException.class);

    // WHEN/THEN
    assertThrows(NotRegisteredException.class,
            () -> renterRegistrar.getRegisteredRenter(driverLicenseId));
}

Overview

In a previous lesson, we introduced unit testing to assess the methods in our classes. Sometimes the classes we want to test are tightly intertwined with the functionality of other classes. This can make our unit testing complicated. We might not be able to test all the precise test conditions we desire. In this lesson, we're going to introduce mocking. Mock objects are dummy implementations of our dependencies that allow us to define the output of their method calls.

In the first reading, we'll introduce mocking concepts and why we want to use them. We'll then set up an example that we'll be using throughout this lesson to show mocking in code. By the end of this reading, we'll have our mock and test objects set up.
In the second reading, we'll continue using our mocking example to show how to write deterministic tests. We'll explore how to determine what to mock and how to turn that logic into code. We'll also discuss additional methods that give greater flexibility over our deterministic tests and outputs.

What are mocks?

Mock objects are dummy objects that simulate the behavior of real objects in a controlled way. They're used to test the behavior of some other object. For example, when testing the safety functions of a car, we insert a crash test dummy to simulate the behavior of a real person in a crash. This gives us a good idea of how the safety features will protect a real person without having to endanger anyone.

Similarly, we can mock objects in Java. Mock objects allow us to mock, or imitate, the functionality of an object's method when writing tests. For example, say we have an OrderDao class. It has a method getOrder that retrieves an order from the database. We can create a mocked object of this class and force the getOrder method to return an Order without ever hitting the database.

Why do we mock?

Mocking is essential for writing unit tests. A unit test is intended to test a single method of a single class at a time. But why would we want to mock objects? How does this help us test the functionality of a class? Most of the classes we write have dependencies. A dependency is when a class in Java depends on an instance of another class. For example, if we create a card game with the classes Card and Deck, the Deck class will have many instances of the Card class, making the Deck class dependent on the Card class. When we unit test methods from the Deck class, we're unfortunately relying on the functionality of its dependencies; the Card class. This isn't ideal. When unit testing, we want our unit tests to test just a single unit, not the unit and the code it depends on.

Keeping unit tests independent from dependencies also helps ensure a few different things. It makes sure our tests are deterministic and capable of testing hard to reproduce use cases. We also want tests to be fast, free, and clear of false alarms. We'll describe each of these benefits below.

Deterministic tests always produce the same result from a given starting condition or initial state. Consider an Alexa skill class that contains a method, getTemperature, to return the temperature for a provided location. The Alexa skill class is dependent on the weather service because it relies on it to get the current temperature. The getTemperature method checks the user's preferences for the temperature scale (Fahrenheit or Celsius) and requests the temperature from the weather service using the preferred scale. It then returns the temperature at the requested location. A unit test might verify that when the getTemperature method is asked for the temperature in Seattle by a user that prefers the Fahrenheit scale, it returns the correct temperature. If we called the real weather service for each run of this unit test, the results would always change like the weather in Seattle changes. This means we can't write a deterministic unit test. We can't assert that the temperature returned is equal to anything! If we did, it's likely our unit tests would constantly be failing.

In the case of the Alexa skill class method, getTemperature, behaving correctly really means requesting the temperature with the correct temperature scale from the weather service and returning it. The temperature being correct is the job of the weather service. (It better have its own unit tests!). So, we can mock the response from the weather service to ensure we write a deterministic test. We can force the weather service to always return 75°F when it's asked for the weather in Seattle by a user that prefers Fahrenheit. Then your unit tests can depend on this behavior.

With mocking, we can also test states that are hard to reproduce. For example, let's say we're testing a home alarm system class. When the alarm system is triggered, the class makes three calls to the alarm class to check if the homeowner has disabled the alarm. We want to test the case where the first two times the alarm class was checked, the alarm was not disabled, but on the third call, it was disabled by the customer. It will be tricky to set this situation up in our test class since we need some external intervention at just the right time (the customer disabling). However, we need to make sure this case works as it's important to prevent unnecessary calls to law enforcement. If we use a mock alarm, we can mock the functionality that on the first two calls it returns not disabled, but on the third call, it returns disabled.

Mocking also ensures our tests are fast. Imagine we're trying to test an ATM class, which relies on a bank connection. It can take up to 30 seconds to make a connection with the bank and let us know if a customer has enough money in their account to make a withdrawal. This may be fine if we only have one or two tests to run, but what if we have twenty tests to run using the bank connection! It will take ten minutes every time we have to run our tests, which would be a debugging nightmare! Instead, we can use a mock connection that returns a predetermined response and our tests will run in seconds.

Finally, mocking allows our tests to be free. Let's say we work on a service that uses a machine learning model. Each time our service starts the machine learning algorithm, its dependency spins up an expensive host, costing us about $14/hour to run. If our model takes six hours to build, we're spending $84 each time our tests run! Mocking the dependency means that we never spin up the expensive host costs and our tests are free to run! We also mentioned tests should be free of false alarms. In the above home alarm system class, imagine what happens when the alarm is triggered. Ideally, it should call the police. However, we don't want to call the police each time we run our unit tests.

You may be wondering when we test how these classes work together? Before we release it to our customers, it seems like we'd want to know the whole system actually works and not just each individual piece. You're right! That's called integration testing, and we'll cover it in a later unit at ATA.

Example scenario

Now that we understand what mocking is and why we use it, let's talk about how we actually create a mock! We're going to set up an example to show what mocking looks like in code.

Imagine we work for a rental car company. In order to rent a car from the company, you need to be a registered renter. For the purpose of this activity, a customer needs to have a valid driver's license and be 18 or older to register. We created a class, RenterRegistrar, that has two methods: getRegisteredRenter() and registerRenter(). The getRegisteredRenter() method accepts a driver's license and returns the driver's information if the license exists in the list of registered renters. If not, the method throws a NotRegisteredException. The registerRenter() method accepts a driver, checks their age, and checks to see if they have a valid driver's license. If they meet conditions, they're added to the list of registered renters. If they don't, the method throws a NotEligibleToRegisterException. The RenterRegistrar class looks like the following:


public class RenterRegistrar {

    private RenterDao renterDao;
    private DriverLicenseService driverLicenseService;

    public RenterRegistrar() {
        this.renterDao = new RenterDao(new ArrayList<>());
        this.driverLicenseService = new DriverLicenseService();
    }

    public Driver getRegisteredRenter(String driverLicenseId) throws NotRegisteredException {
        return renterDao.getRegisteredRenter(driverLicenseId);
    }

    public void registerRenter(Driver driver) throws NotEligibleToRegisterException {
        if (driver.getAge() < 18) {
            throw new NotEligibleToRegisterException(driver.getName() + " is under 18.");
        }
        else if (!driverLicenseService.isValid(driver.getDriverLicenseId())) {
            throw new NotEligibleToRegisterException(driver.getName() + " doesn't have a valid drivers license.");
        }
        renterDao.addRenter(driver);
    }
}
                

The RenterRegistrar class has two dependencies: DriverLicenseService and RenterDao. The DriverLicenseService class has one method: isValid(). This method checks to see whether a given driver's license ID is valid or not. In an actual application, this method would likely connect to a driver's license database. For the purpose of this activity, the method just checks to see if the given license ID is exactly 10 characters long. The DriverLicenseService class looks like the following:


public class DriverLicenseService {

    public boolean isValid(String driverLicenseId) {
        return driverLicenseId.length() == 10;
    }
}
                

The RenterDao class has two public methods: getRegisteredRenter() and addRenter(). The RenterDao class also has an instance variable: renterList, which is an ArrayList of type Driver that keeps track of the people registered to rent a car. When given a driver's license id, the getRegisteredRenter() method iterates through renterList to find a specific Driver. If the driver's license id can't be found in renterList, the method throws a NotRegisteredException instead. The addRenter() method adds a given driver to the renterList. The renterList is persisted in a file so that we can track renters across each run of the program. The RenterDao class looks like the following:


public class RenterDao {

    private Map<String, Driver> renterLicenseIdLookup;

    public RenterDao() throws RenterDatabaseNotAvailableException {
        this.renterLicenseIdLookup = getRentersFromFile();
    }

    public Driver getRegisteredRenter(String driverLicenseId) throws NotRegisteredException {
        if (!renterLicenseIdLookup.containsKey(driverLicenseId)) {
            throw new NotRegisteredException(String.format("Driver with licenseId: " +
                    "%s is not registered to rent a car.", driverLicenseId));
        }

        return renterLicenseIdLookup.get(driverLicenseId);
    }

    public void addRenter(Driver driver) throws RenterDatabaseNotAvailableException {
        if (!renterLicenseIdLookup.containsKey(driver.getDriverLicenseId())) {
            renterLicenseIdLookup.put(driver.getDriverLicenseId(), driver);
            addRenterToFile(driver);
        }
    }

    private void addRenterToFile(Driver driver) {
        // implementation not shown
    }

    private List<Driver> getRentersFromFile() {
        // implementation not shown
    }
}
                

The RenterDao class is dependent on the Driver class, which is a POJO for a driver. The Driver class is defined below:


public class Driver {

    private String name;
    private int age;
    private String driverLicenseId;

    public Driver(String name, int age, String driverLicenseId) {
        this.name = name;
        this.age = age;
        this.driverLicenseId = driverLicenseId;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getDriverLicenseId() {
        return driverLicenseId;
    }

    // equals and hashcode implementations omitted
}
                

We want to unit test the two methods in the RenterRegistrar class to make sure they're functioning as we expect. To test the methods, we write a test class RenterRegistrarTest. For the purpose of this activity we're going to focus on three tests:

  • getRegisteredRenter_registeredDriver_returnsDriver validates the getRegisteredRenter method returns the associated Driver when the driver has already been registered
  • getRegisteredRenter_unregisteredDriver_throwsNotRegisteredException validates the isRegisteredRenter method throws NotRegisteredException when the driver has not been registered
  • registerRenter_eligibleDriver_successfullyRegisters validates that an eligible-for-registration driver can successfully register

Our test class is defined below without mocking:


public class RenterRegistrarTest {

    private RenterRegistrar renterRegistrar;

    @BeforeEach
    public void setup() {
        renterRegistrar = new RenterRegistrar();
    }

    @Test
    public void getRegisteredRenter_registeredDriver_returnsDriver() throws RenterDatabaseNotAvailableException, NotEligibleToRegisterException, NotRegisteredException {
        // GIVEN
        String driverLicenseId = "1234ABC567";
        Driver driver = new Driver("Danica Patrick", 38, driverLicenseId);
        renterRegistrar.registerRenter(driver);

        // WHEN
        Driver registeredRenter = renterRegistrar.getRegisteredRenter(driverLicenseId);

        // THEN
        assertEquals(driver.getName(), registeredRenter.getName());
        assertEquals(driver.getAge(), registeredRenter.getAge());
        assertEquals(driver.getDriverLicenseId(), registeredRenter.getDriverLicenseId());
    }

    @Test
    public void getRegisteredRenter_unregisteredDriver_throwsNotRegisteredException() {
        // GIVEN
        String driverLicenseId = "ABC1234567";

        // WHEN
        // THEN
        assertThrows(NotRegisteredException.class,
                () -> renterRegistrar.getRegisteredRenter(driverLicenseId));
    }

    @Test
    public void registerRenter_eligibleDriver_successfullyRegisters() throws NotEligibleToRegisterException, RenterDatabaseNotAvailableException, NotRegisteredException {
        // GIVEN
        String driverLicenseId = "1234ABC567";
        Driver driver = new Driver("Danica Patrick", 38, driverLicenseId);

        // WHEN
        renterRegistrar.registerRenter(driver);

        // THEN
        Driver registeredRenter = renterRegistrar.getRegisteredRenter(driverLicenseId);
        assertEquals(driver.getName(), registeredRenter.getName());
        assertEquals(driver.getAge(), registeredRenter.getAge());
        assertEquals(driver.getDriverLicenseId(), registeredRenter.getDriverLicenseId());
    }
}
                

Creating Mocks

Our unit tests are set up and are currently using the actual implementations of DriverLicenseService and RenterDao. There's a couple of pitfalls to doing this! When we register a renter, the RenterDao actually stores it in the file, persisting test data. Putting our test data into the real database means that our real data is polluted and unreliable until we go in and clean it up. Additionally, the test could break if the test data we use conflicts with a real driver being registered.

When we test getting a registered driver, we also have to register the driver in the RenterDao before we can run the test. Otherwise, we can't depend on the driver being registered! This means we're no longer testing a "unit" of code; instead, we are testing both methods, as a failure in either method would fail the test.

However, we can switch to using mocks! As we said previously, we want to mock the objects that our class under test is dependent on. Our class under test is RenterRegistrar and its dependencies are DriverLicenseService and RenterDao so we want to mock instances of those two classes.

Java has a few different mocking frameworks, but we'll use Mockito. It's a popular mocking framework, which can conveniently be used in conjunction with JUnit. The Mockito framework allows us to create and configure mock objects. It greatly simplifies the development of tests for classes with external dependencies.

There are three things that we need to do to use Mockito in our unit tests:

  1. Mock the dependencies and insert the mocks into the test code
  2. Execute the test code
  3. Validate that the code executes as expected

That's it! Let's start by discussing the first step: mock the dependencies and insert the mocks into the test code. We start by adding the variable(s) we want to mock to our test class. To create mocked instances of our dependencies RenterDao and DriverLicenseService, we add them to our test class and annotate them with @Mock. This annotation is used to create mocked instances. We've now added the following lines to the top of our test class:


@Mock
private RenterDao renterDao;

@Mock
private DriverLicenseService driverLicenseService;
                

Now we have our mocked dependencies set up, there are still a few more things we need to do. Next, we're going to add the @InjectMocks annotation above the class variable we're testing, which is RenterRegistrar. We'll learn a bit more about injection in a later lesson, so for now, consider @InjectMocks as a little bit of magic. Basically, it tells Mockito this is the class that we want to swap out its real dependencies with mock dependencies. The mock objects are passed to this class' constructor to create a new object. When we add in this annotation, our RenterRegistrar instance declaration looks like the following:


@InjectMocks
private RenterRegistrar renterRegistrar;
                

The final step is to update our setup() method, which creates the class under test object. We will remove creating a new RenterRegistrar using its constructor, and instead call a Mockito method, initMocks(). It's a static method that initializes the mocks for this test class object (anything annotated with @Mock) by passing in the keyword this. It then creates an object of the class under test (annotated with @InjectMocks), injecting it with the mock objects. The @BeforeEach annotation that we use states that the renterRegistrar object and its mocks will be recreated at the start of every test method. This is a best practice to prevent tests from interfering with each other. Our setup() method now looks like the following:


@BeforeEach
public void setup() {
    initMocks(this);
}
                

Now our mocked dependencies are set up! Let's look at all the changes we made together:


public class RenterRegistrarTest {

    @InjectMocks
    private RenterRegistrar renterRegistrar;

    @Mock
    private RenterDao renterDao;

    @Mock
    private DriverLicenseService driverLicenseService;

    @BeforeEach
    public void setup() {
        initMocks(this);
    }

    //...test methods
}
                

Before we wrap up this reading, there's one more issue with our code to resolve. The constructor of the class we're testing, RenterRegistrar, currently looks like the following:


public RenterRegistrar() {
    this.renterDao = new RenterDao();
    this.driverLicenseService = new DriverLicenseService();
}
                

When we initialize the instance of renterRegistrar in our test class, the above constructor creates new instances of RenterDao and DriverLicenseService even though we'll use the mocked versions we created in our test class. Mockito is actually smart enough to still use the mocked versions. If the constructor doesn't have any parameters, Mockito can still figure it out. However, this might confuse readers of our code because we're creating objects we'll never use. It's a good design to accept our dependencies via our constructor instead of creating them in the constructor, as we learned from Dependency Injection.

To fix this issue, we'll update our constructor to instead accept an instance of RenterDao and DriverLicenseService.


public RenterRegistrar(RenterDao renterDao, DriverLicenseService driversLicenseService) {
    this. renterDao = renterDao;
    this.driverLicenseService = driversLicenseService;
}
                

In our test class, we don't need to make any adjustments. However, Mockito will now use our constructor to pass the mocked objects to our object under test, rather than going about it in a more hacky way.

Summary

In this reading, we introduced the concept of mocking in unit tests to avoid relying on dependencies when testing a class. We also learned about Mockito, which is a popular mocking framework that can conveniently be used in conjunction with JUnit. With our car rental registration application, we set up a code example to demonstrate how to mock dependencies using @Mock. We provided them to our object under test by using @InjectMocks, and we triggered Mockito to set everything up using initMocks. We also discussed refactoring our code so that a constructor accepts its dependencies through its constructor.

Mocking Behavior

Overview

In a previous lesson, we introduced unit testing to assess the methods in our classes. Sometimes the classes we want to test are tightly intertwined with the functionality of other classes. This can make our unit testing complicated. We might not be able to test all the precise test conditions we desire. In this lesson, we're going to introduce mocking. Mock objects are dummy implementations of our dependencies that allow us to define the output of their method calls.

In the first reading, we introduced mocking concepts and why we want to use them. We set up an example we'll use throughout this lesson to show mocking in code. At the end of the reading, we had our mock and test objects set up.
In the second reading, we'll continue using our mocking example to show how to write deterministic tests. We'll explore how to determine what to mock and how to turn that logic into code. We'll also discuss additional methods that give greater flexibility over our deterministic tests and outputs.

Defining behavior

In the last reading, we covered mocking dependencies, but we didn't update our actual unit tests to use the mocks! If we tried to run our unit tests now, some would fail and some will run into exceptions. Unless otherwise specified, mock objects will return default values when their methods are called. For Java objects, the default value is null. For the primitive type boolean, it's false. You can use Oracle's documentation to review the default return values for each primitive typeLinks to an external site.. So, when isValid is called on our mock DriverLicenseService object, regardless of the input, it returns false.

So how do we specify the values we want instead of just using the default values? To guarantee deterministic results for our tests, we need to tell our mocks how to behave. We can tell our mocks that when they see a certain method called with a certain input, then they should return a certain output. This helps us test the functionality of our methods without worrying about the behavior of the dependencies.

The first thing we need to determine is what to mock. Let's look at our first test method again:


    @Test
    public void getRegisteredRenter_registeredDriver_returnsDriver() throws RenterDatabaseNotAvailableException, NotEligibleToRegisterException, NotRegisteredException {
        // GIVEN
        String driverLicenseId = "1234ABC567";
        Driver driver = new Driver("Danica Patrick", 38, driverLicenseId);
        renterRegistrar.registerRenter(driver);

        // WHEN
        Driver registeredRenter = renterRegistrar.getRegisteredRenter(driverLicenseId);

        // THEN
        assertEquals(driver.getName(), registeredRenter.getName());
        assertEquals(driver.getAge(), registeredRenter.getAge());
        assertEquals(driver.getDriverLicenseId(), registeredRenter.getDriverLicenseId());
    }
                

By looking at the title of the method, we know that our test case validates that when a registered driver's license id is passed to getRegisteredRenter() it should return the Driver. So, we want the driver's license id we pass in to getRegisteredRenter() to correspond with someone who's already registered to rent a car.

Let's look at the method under test:


    public Driver getRegisteredRenter(String driverLicenseId) throws NotRegisteredException {
        return renterDao.getRegisteredRenter(driverLicenseId);
    }
                

Looking at the method, in order to have the driver's license we pass in correspond to someone who is already registered to rent a car, we need renterDAO.getRegisteredRenter() to return without throwing an exception.

Finally, let's look at the renterDAO.getRegisteredRenter() method header to see what it should return:


Driver getRegisteredRenter(String driverLicenseId) throws NotRegisteredException
                

We see that we need to return the driver that corresponds to the provided driver's license id.

Following the pattern we previously mentioned, we want to tell our mock that when it sees renterDAO.getRegisteredRenter() called with a certain input, then it should return a Driver instance. We can use the Mockito methods when() and thenReturn() to mock the behavior of our RenterDao.

The line of code looks like the following:


when(renterDao.getRegisteredRenter(driverLicenseId)).thenReturn(driver);
                

We pass getRegisteredRenter(driverLicenseId), because that's the value we expect to be passed to the method. The method under test will be called with driverLicenseId, and then will call getRegisteredRenter with that same value. (Warning: If you provide the wrong input when setting up the mock's when method, Mockito will not return the value provided to the thenReturn method. Mockito will instead return the default since the mocked behavior we set up never actually occurred.)

Without mocking, we were setting up a registered driver in our GIVEN section by making a call to register the renter:


renterRegistrar.registerRenter(driver);
                

Instead, we can replace that line with our new mocked behavior. Now we don't actually have to register a driver for our tests to work.

We have one last change to make. When calling renterRegistrar.registerRenter() we had to handle the checked exception it threw, NotEligibleToRegisterException. We removed this call, so we can remove this exception from the throws clause. The call we are mocking, renterDao.getRegisteredRenter() also throws a checked exception, NotRegisteredException. We'll need to add that to the throws clause too. Our first test method passes now and looks like this:


@Test
public void getRegisteredRenter_registeredDriver_returnsDriver() throws NotRegisteredException {
    // GIVEN
    String driverLicenseId = "1234ABC567";
    Driver driver = new Driver("Danica Patrick", 38, driverLicenseId);
    when(renterDao.getRegisteredRenter(driverLicenseId)).thenReturn(driver);

    // WHEN
    Driver registeredRenter = renterRegistrar.getRegisteredRenter(driverLicenseId);

    // THEN
    assertEquals(driver.getName(), registeredRenter.getName());
    assertEquals(driver.getAge(), registeredRenter.getAge());
    assertEquals(driver.getDriverLicenseId(), registeredRenter.getDriverLicenseId());
}
                

Defining exceptional behavior

Sometimes instead of returning a value, our mocked behavior will need to throw an exception. For example, let's take a look at our second unit test:


@Test
public void getRegisteredRenter_unregisteredDriver_throwsNotRegisteredException() {
    // GIVEN
    String driverLicenseId = "ABC1234567";

    // WHEN
    // THEN
    assertThrows(NotRegisteredException.class,
            () -> renterRegistrar.getRegisteredRenter(driverLicenseId));
}
                

Looking at the title of this method, we know our test case validates that when an unregistered driver's license id is passed to getRegisteredRenter() it should throw a NotRegisteredException. So, we want the driver's license we pass in to not belong to a registered renter.

Let's once again look at the method we're testing:


public Driver getRegisteredRenter(String driverLicenseId) {
    return renterDao.getRegisteredRenter(driverLicenseId);
}
                

For the getRegisteredRenter() method to throw the NotRegisteredException, the renterDao.getRegisteredRenter() method must throw a NotRegisteredException.

Using a very similar process to above, we tell our mock that when it sees renterDAO.getRegisteredRenter() called with a certain input, then it should throw a NotRegisteredException. Notice that in this test we want to throw an exception instead of return a value. We can use the Mockito methods when() and thenThrow() to mock the behavior of our RenterDao.

This highlights a great advantage of mocking. We didn't have to worry about the specific conditions that would cause a NotRegisteredException, and set up data to make the exception occur. We simply have to say, please throw this exception when you see a certain driver's license id.

The line of code looks like the following:


when(renterDao.getRegisteredRenter(driverLicenseId)).thenThrow(NotRegisteredException.class);
                

Again, we pass getRegisteredRenter(driverLicenseId), because that's the value we expect to be passed to the method. The method under test will be called with driverLicenseId, and then will call getRegisteredRenter with that same value.

Once again, we need to add throws NotRegisteredException to the method signature since renterDao.getRegisteredRenter() throws this checked exception. This line of code should be added to the GIVEN portion of our test.

The full unit test looks like the following:


@Test
public void getRegisteredRenter_unregisteredDriver_throwsNotRegisteredException() throws NotRegisteredException {
    // GIVEN
    String driverLicenseId = "ABC1234567";
    when(renterDao.getRegisteredRenter(driverLicenseId)).thenThrow(NotRegisteredException.class);

    // WHEN
    // THEN
    assertThrows(NotRegisteredException.class,
            () -> renterRegistrar.getRegisteredRenter(driverLicenseId));
}
                

The syntax changes slightly when we want to mock an exception being thrown from a void method. For example, imagine the RenterDao instead had a method void validateDriver(String driverLicenseId) that instead of returning a value, threw an exception if the id could not be found and otherwise returned successfully. Instead of using thenThrow(), we would use the Mockito method doThrow() and chain on the when() method. The other difference is that we specify the method to be called on the mock (in this case validateDriver()) outside of the when() method instead of as part of the parameters.

This line of code would look like the following:


doThrow(NotRegisteredException.class).when(renterDAO).validateDriver(driverLicenseId);
                

Verifying behavior

Let's take a look at our third unit test method:


@Test
public void registerRenter_eligibleDriver_successfullyRegisters() throws NotEligibleToRegisterException, RenterDatabaseNotAvailableException, NotRegisteredException {
    // GIVEN
    String driverLicenseId = "1234ABC567";
    Driver driver = new Driver("Danica Patrick", 38, driverLicenseId);

    // WHEN
    renterRegistrar.registerRenter(driver);

    // THEN
    Driver registeredRenter = renterRegistrar.getRegisteredRenter(driverLicenseId);
    assertEquals(driver.getName(), registeredRenter.getName());
    assertEquals(driver.getAge(), registeredRenter.getAge());
    assertEquals(driver.getDriverLicenseId(), registeredRenter.getDriverLicenseId());
}
                

Looking at the name of this method, we know that our test case validates that when an eligible driver is passed to registerRenter() it should successfully register the given driver. So, we want the driver we pass in to registerRenter() to correspond with someone who is allowed to register.

Let's look at the method we're testing:


public void registerRenter(Driver driver) throws NotEligibleToRegisterException {
    if (driver.getAge() < 18) {
        throw new NotEligibleToRegisterException(driver.getName() + " is under 18.");
    }
    else if (!driverLicenseService.isValid(driver.getDriverLicenseId())) {
        throw new NotEligibleToRegisterException(driver.getName() + " doesn't have a valid drivers license.");
    }
    renterDao.addRenter(driver);
}
                

This method is a bit more complex than the previous one we tested. As you can see, this method is dependent on a method from both driverLicenseService and renterDao. The registerRenter() method successfully registers a person when they are 18 or older and their driver's license is valid. For a driver's license to be valid, it must return true when passed into the driverLicenseService.isValid() method. Once a person passes both those tests, they can then successfully register using the renterDAO.addRenter() method.

Let's first look at the driverLicenseService.isValid() method header to see what it should return:


boolean isValid(String driverLicenseId)
                

Looking at this method, we can see that it returns true when a driver's license is valid, false otherwise. We want to tell our mock that when it sees driverLicenseService.isValid() called with a certain input, then it should return true. The certain input should be the driver's license id of the driver passed to our method under test. We can add the line below to our GIVEN section to mock this behavior.


when(driverLicenseService.isValid(driverLicenseId)).thenReturn(true);
                

Next, we need to look at the renterDao.addRenter() method signature:


void addRenter(Driver driver)
                

Looking at this method, we can see that it's void and doesn't return anything. Since nothing is being returned, we can't use the thenReturn() method. Instead, we'll verify that the method gets called when our method under test is called.

What we care about here is that the addRenter() method is called and that it's called with the right input. In this case, the correct input is the driver we initially passed to the registerRenter() method.

To verify the addRenter() method is called with the right input, we use the Mockito verify() method. We chain the method we're checking (in this case addRenter()) on to the verify() method and pass in the expected input.

This line of code looks like the following:


verify(renterDao).addRenter(driver);
                

Verifications go in the THEN section of our test. We can only verify that an interaction with a mock occurred after we call the method that causes the interactions. In this case, renterRegistrar.registerRenter() needs to be called for the call to renterDao.addRenter() to occur. We can replace our assertTrue statement with this verification statement. To validate that renterRegistrar.registerRenter() is correct, we only need to verify that it calls the RenterDao to add our driver. Since we are using mocks though, the driver won't actually be persisted in the RenterDao, so our previous assert statement won't be very valuable.

The full unit test looks like the following:


@Test
public void registerRenter_eligibleDriver_successfullyRegisters() throws NotEligibleToRegisterException {
    // GIVEN
    String driverLicenseId = "1234ABC567";
    Driver driver = new Driver("Danica Patrick", 38, driverLicenseId);
    when(driverLicenseService.isValid(driverLicenseId)).thenReturn(true);

    // WHEN
    renterRegistrar.registerRenter(driver);

    // THEN
    verify(renterDao).addRenter(driver);
}
                

Overloaded verify method

The verify() method also has an overloaded method that allows us to set a VerificationMode. There are numerous VerificationMode static methods. A common method is times(), which tracks the number of times we call a method. We could verify that the addRenter() method is only called one time with the following line of code:


verify(renterDao, times(1)).addRenter(driver);
                

If times(X) is not provided, verify will default to verifying the method was called once.

We can also use the verifyZeroInteractions() method, which verifies that none of a mock's methods were called. For example, in our first and second unit tests, we could ensure that no methods from the driverLicenseService mock were called with:


verifyZeroInteractions(driverLicenseService);
                

Some other common VerificationMode methods are:


// Check to make sure this method with this input was called >= wantedNumberOfInvocations times:
atLeast(int wantedNumberOfInvocations)

// Check to make sure this method with this input was called <= wantedNumberOfInvocations times:
atMost(int wantedNumberOfInvocations)

//Check to make sure this method with this input was called >= 1 times:
atLeastOnce()

//Check to make sure this method with this input was never called:
never()
                

What not to mock

Now we've taught you how to mock and why, but it's also important to understand what not to mock. Mocking is very effective, but only when used correctly. We want to make sure that we're mocking roles not objects. The role is the interface whereas the object is the implementation of the interface. If our class has a dependency on an interface type, we want to mock the interface, not the implementation that our class uses.

We always need to make sure we don't mock code that we don't understand. Mocks allow us to describe the behavior of other code. If we describe that behavior incorrectly or incompletely, we can't verify that code works in real conditions. If we don't understand all the parts and pieces of the code, we may not be able to verify the code works in all conditions. In general, only mock code we understand well, such as code we own. We only mock code's well-documented behavior, and we don't mock code without a reason.

Avoid mocking collection classes such as List and Map. Collection classes often include hidden behaviors we can't mock or aren't fully documented. These classes are normally well tested anyways, so there shouldn't be a reason for us to mock them.

Lastly, probably don't mock POJOs. This is debated in the I.T. community, but most companies, generally, don't mock POJOs. The purpose of mocking is to hide another class' business logic and focus on functionality. POJOs shouldn't have business logic and therefore shouldn't require mocking.

Conclusion

In this reading, we covered writing deterministic tests by using mocks to force dependable behavior from our dependencies. We mapped out the logic for determining what to mock for each unit test, and how to turn that into code. We also learned about matchers, which allow us to mock even when we don't know the exact value being passed into a method. Finally, we discussed what not to mock. Mocking is a powerful tool that can help save time and money when testing, while also providing control over our test conditions.

Guided Project

Complete this project to practice your mocking skills.

Rack Monitor

Resources