DEV Community

Cover image for To mock or to stub, that is the question
Pawel Pawlak
Pawel Pawlak

Posted on

To mock or to stub, that is the question

This post is going to tell you some basic stuff about testing terminology, with some simple examples. Hope you will find it interesting, especially when there is some misunderstanding going on online between mocks and stubs, so lets dive into it!

All tests that we are going to talk about are called *test doubles *– how come ? It is taken from the terminology used in the move/film industry.

In the movie when any dangerous or potentially risky action is about to be performed by the main actor/character, director is calling the stunt double to take over.

Basically it means that instead of using real objects (actors), in the testing scenario we are going to use test doubles (not real production data), just to test the logic of given implementation.

The most important thing to remember is that test doubles are divided in to two main groups:

  • mocks
  • stubs

and you should always know the difference between those those.

Stub

We use stub if we want to return specific data for in a given circumstances. In other words we teach this object to return specific value for given input, e.g. when code reaches this place in the algorithm return value of 100. We do not want to use real data, we want to use a test data, that checks the logic of our algorithm.

Lest have an example that tests if a rented car has reached its maximum limit of allowed distance to be made. This information can be further used for sending some warning info to the customer for example.

This is our CarSatatistic class

public class CarStatistic {
    private final int tripDistance;
    private final float averageConsumption;
    private final int maxSpeed;
    public CarStatistic(int tripDistance, float averageConsumption, int maxSpeed) {
        this.tripDistance = tripDistance;
        this.averageConsumption = averageConsumption;
        this.maxSpeed = maxSpeed;
    }
    public int getTripDistance() {
        return tripDistance;
    }
    public float getAverageConsumption() {
        return averageConsumption;
    }
    public int getMaxSpeed() {
        return maxSpeed;
    }
}
Enter fullscreen mode Exit fullscreen mode

then a simple validator

public class CarRentValidator {
    private static final int MAX_DISTANCE_ALLOWED = 500;
    public boolean isMaximumDistanceReached(Car car){
        return car.getTripDistance() > MAX_DISTANCE_ALLOWED;
    }
}
Enter fullscreen mode Exit fullscreen mode

and a finally test with a use of a stub:

@ExtendWith(MockitoExtension.class)
public class CarTest {
    @Mock
    private Car car;
    @Test
    public void ifTripDistanceIsOverMaximumValue_thenValidatorShouldReturnTrue(){
        Mockito.when(car.getTripDistance()).thenReturn(600);
        CarRentValidator carRentValidator = new CarRentValidator();
        assertTrue(carRentValidator.isMaximumDistanceReached(car));
    }
}
Enter fullscreen mode Exit fullscreen mode

This test tests the validator that should return true if user/driver has reached the maximum number of kilometers.

Stubs are divided into three groups:

  • Stubs (the one I have introduced before)
  • Fake
  • Dummy

Fake

Fake objects are often use when using real implementation is not possible usually because of the time that is needed to generate/use real objects, or because of the architecture itself. Fakes often are much more simpler then the real implementation.

They can be also used in the situations when we actually do not have the production code ready yet, but we want to test some logic.

Very common practice is to use fakes as the in memory DB. Usually our repository (dao) implements some interfaces, and when we want to test some behaviour, we use our test class implementation, that we call fake.

Time is one of the most important architecture driver. Running all test in 10 minutes, or running them in 10 seconds can make a very big difference in a project. This is why fakes are some important and are so widely use.

Class CarService that has a method to find a closest available car in given range called findClosestCar.

public final class CarService {
    private CarRepository carRepository;
    private UserRepository userRepository;
    private Notifier smsNotificationService;
    public CarService(){}
    public CarService(CarRepository carRepository, UserRepository userRepository, Notifier smsNotificationService){
        this.carRepository = carRepository;
        this.userRepository = userRepository;
        this.smsNotificationService = smsNotificationService;
    }
    public Optional<CarStatistic> findClosestCar(float range){
        return carRepository.findClosestCar(range);
    }
    public boolean sentNotificationWhenLimitReached(CarStatistic carStatistic, User user){
        CarRentValidator carRentValidator = new CarRentValidator();
        if (carRentValidator.isMaximumDistanceReached(carStatistic)){
            return this.sendNotification(user);
        }
        return false;
    }
    public boolean sendNotification(User user){
        // smsNotificationService.notify();
        return true;
    }
}
Enter fullscreen mode Exit fullscreen mode

then there is a CarRepository contract,

public interface CarRepository {
    Optional<Car> findClosestCar(float range);
}
Enter fullscreen mode Exit fullscreen mode

and it’s fake implementation

public class FakeCarRepository implements CarRepository {
    @Override
    public Optional<CarStatistic> findClosestCar(float range) {
        if (range < 100) {
            return Optional.of(new CarStatistic(500, 5.5F, 137));
        }
        return Optional.empty();
    }
}
Enter fullscreen mode Exit fullscreen mode

and a test to see if there is available car in given range.

@ExtendWith(MockitoExtension.class)
public class CarStatisticsFakeDummyTest {
    private final UserRepository userDummyRepository = (id) -> Optional.empty();
    private final Notifier notifierDummy = (user) -> {
    };
    private final CarService carService = new CarService(new FakeCarRepository(), userDummyRepository, notifierDummy);
    @Test
    public void whenCarIsInRange_ItIsFound() {
        assertTrue(carService.finClosestCar(90).isPresent());
    }
    @Test
    public void whenCarIsNotInRange_ItIsNotFound() {
        assertFalse(carService.finClosestCar(190).isPresent());
    }
}
Enter fullscreen mode Exit fullscreen mode

Dummy

We use dummy just to allow code to compile. It is simply something that must be given, e.g. passed as a parameter to a method, so the code runs/compiles without any errors.

In the CarStatisticsFakeDummyTest class it is e.g. parameter with name userDummyRepository that is passed to the constructor of the CarService. We do not carry about its implementation at all. It simply is, so code compiles.

Mocks

Mock are used when we want to verify if given action was executed. We are not interested in the result at all, but just whether the given method/function was called. Verification of call can be done starting from the simple single method call, to some advance algorithm checks that uses (calls) several steps (methods) to finish – we can check whether all those steps were executed.

In the example below we are going to test whether a driver got an SMS that is sent to him only when he reaches the maximum number of kilometers that he is allowed to drive in a rented car.

public class CarStatisticsMockTest {
    @Test
    public void whenLimitIsReached_smsNotificationIsSent() {
        CarService carService = new CarService();
        CarStatistic carStatistic = new CarStatistic(600, 7L, 185);
        User user = new User(123, "3442-2333-22331");
        Assertions.assertTrue(carService.sentNotificationWhenLimitReached(carStatistic, user));
    }
}
Enter fullscreen mode Exit fullscreen mode

Mocks are divided into two groups:

  • Mock – the one I have already explained
  • Spy

Spy

Spy is very similar to the mock, it is actually its extension, because it not only checks whether method was called, but also verify the number of calls of this method. It can be extremely helpful if you are testing, and trying to figure out how many times some method is calling the DB.

Here is an example of verifying the number of calls for sending sms method.

@ExtendWith(MockitoExtension.class)
public class CarStatisticsSpyTest {

    @Spy
    private CarService carServiceSpy;
    @Test
    public void whenLimitIsReached_smsNotificationIsSentJustOnce() {
        CarStatistics carStatistics = new CarStatistics(600, 7L, 185);
        User user = new User(123, "3442-2333-22331");
        carServiceSpy.sentNotificationWhenLimitReached(carStatistics, user);
        verify(carServiceSpy, times(1)).sendNotification(user);
    }
}
Enter fullscreen mode Exit fullscreen mode

Stubs vs Mocks

In general we use mocks for testing commands – we check if this method was executed, and e.g with what parameters it was called.

Stubs are much more often used for testing queries – we want to test out search engine for example, if for given input parameters it returns expected values.

There can be a situations that we want to use mocks for testing queries as well. They can be used e.g. when we want to check the number of calls to the external server, or to the DB. Sometimes the call using external API is quite expensive, and we want to clarify the number of those that are required for given feature, that we are implementing.

I hope now you wont make mistake when talking about stubs, and mocks.

Code for this example can be found in here.

Top comments (2)

Collapse
 
priteshusadadiya profile image
Pritesh Usadadiya

[[..PingBack..]]
This article is curated as a part of #63rd Issue of Software Testing Notes Newsletter.

Collapse
 
developersmill profile image
Pawel Pawlak

Thank you!