Stub vs Mock vs Spy

spyCoverPhoto

In the following article, three auxiliary objects used in unit tests – stub, mock and spy – will stand in front of each other in the ring. You’ll learn why we use these types of objects, see examples of them, and learn about the differences between them.

Let’s say you’re co-creating an online store. Users will need to create an account to use the service. If you enter an address when you create it, the account becomes active. We would also like to be able to download customer accounts from a database, e.g . to send you a newsletter. We’ll also create a service class to retrieve and filter accounts from the repository so that only active accounts are returned. So we have the Address, Account, AccountService, and AccountRepository classes. We assume that we do not have access to the code that implements this interface:

public class Address {

    private String street;
    private String number;

    public Address(String street, String number) {
        this.street = street;
        this.number = number;
    }
}
public class Account {

    private boolean active;
    private Address defaultDeliveryAddress;

    public Account(Address defaultDeliveryAddress) {
        this.defaultDeliveryAddress = defaultDeliveryAddress;
        if(defaultDeliveryAddress !=null) {
            activate();
        } else {
            this.active = false;
        }
    }

    public Account() {
        this.active = false;
    }

    public void activate() {
        this.active = true;
    }

    public boolean isActive() {
        return this.active;
    }
}
public class AccountService {

    private AccountRepository accountRepository;

    public AccountService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    List<Account> getAllActiveAccounts() {</Account>
        return accountRepository.getAllAccounts().stream()
                .filter(Account::isActive)
                .collect(Collectors.toList());
    }

    List<String> findByName(String name) {</String>
        return accountRepository.getByName(name);
    }
}
public interface AccountRepository {

    <Account>GetAllAccounts();</Account>
    List<String> getByName(String name);</String>
}

Consider how we would test our code using title objects.

Stub

Stub is an object that contains an example implementation that mimics the operation of the proper one. Examples of its use include the following situations:

  • we do not have access to a real method of returning data
  • we do not want to involve objects that return real data, which could have adverse side effects (e.g. modification of data in the database)

If you want to test a method that returns all active accounts, we will need data. As I mentioned in the preliminary – we do not have access to them. The stub class comes to the rescue and returns the sample data:

public class AccountRepositoryStub implements AccountRepository{
    @Override
    public List<Account> getAllAccounts() {</Account>
        Address address1 = new Address("Herberta", "33");
        Account account1 = new Account(address1);

        Account account2 = new Account();
        Address address2 = new Address("Piłsudski", "12/8");

        Account account3 = new Account(address2);

        return Arrays.asList(account1,account2,account3);
    }

    @Override
    public List<String> getByName(String name) {</String>
        return null;
    }
}

In the test we will use the above class:

    @Test
    void getAllActiveAccounts() {

        given
        AccountRepository accountRepositoryStub = new AccountRepositoryStub();
        AccountService accountService = new AccountService(accountRepositoryStub);

        when
        List<Account> activeAccounts = accountService.getAllActiveAccounts();</Account>

        then
        assertThat(activeAccounts.size(), is(2));
    }

Stub objects work well for simple methods and examples, but with more test conditions and possible interfaces, they do not work. They can grow to a sizeable size and be heavy to maintain.

Mock

Mock is an object that simulates the operation of an actual object. It allows you to determine what interactions we expect during the tests and then verify whether they have occurred. Using mock, let’s test a situation where the database does not return any data:

    @Test
    void getNoActiveAccounts() {

        given
        AccountRepository accountRepository = mock(AccountRepository.class);
        AccountService accountService = new AccountService(accountRepository);
        given(accountRepository.getAllAccounts()).willReturn(Collections.emptyList());

        when
        List<Account> activeAccounts = accountService.getAllActiveAccounts();</Account>

        then
        assertThat(activeAccounts.size(), is(0));
    }

To create a mock, we used mockito 2, more specifically the mock() function, which uses the name of the class we want to simulate as an argument. The given() and willReturn() methods simulated an action in which, if we call getAllAcounts() on our mock, an empty list will be returned.

Compared to stubs, mocki be created dynamically while the code is in operation and provide more flexibility. They also give much more functionality, such as the verification of method calls (whether they were called, how many times, in what order, with what parameters, etc.).

Spy

Spy is a hybrid object – a mixture of real objects and mocks. Its operation can be tracked and verified. He is distinguished by the fact that the operation of his chosen methods can be mocked. It can therefore be partly a power and a partially normal object.

We will use a new example to discuss its operation. Let’s say we have a Meal class:

public class Meal {
    private int price;
    private int quantity;

    public Meal() {
    }

    public int getQuantity() {
        return quantity;
    }

    public int getPrice() {
        return price;
    }

    int sumPrice() {
        return getPrice() * getQuantity();
    }
}

Using a spy object, we would like to verify the method that sums the cost:

    @Test
    void testTotalMealPrice() {

        given
        Meal meal = spy(Meal.class);
        given(meal.getPrice()).willReturn(15);
        given(meal.getQuantity()).willReturn(3);

        when
        int result = meal.sumPrice();

        then
        then(meal).should().getPrice();
        then(meal).should().getQuantity();
        assertThat(result, equalTo(45));
    }

We used a spy() wrapper to create the object. You may notice that both getPrice() and getQuantity() have a programmed activity, while we have assigned the result returned by the real method to the result variable. Additionally, in the “then” section, method calls could be verified by using the then().should() construct.

We use spy objects when we want to use the true behavior of some methods in an object or when we want to be able to verify method calls while maintaining their true behavior.

A summary of the

After reading this article, you know what stub, mock and spy are, you can pinpoint the differences between different objects and under what circumstances to apply them. The use of such solutions will greatly facilitate the creation of unit tests, as well as prevent unwanted side effects, eg. loss or modification of data in databases.

All the code from the entry is on my Githubie.

Leave a Comment

Your email address will not be published. Required fields are marked *