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;
}
}
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;
}
}
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));
}
}
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;
}
}
then there is a CarRepository contract,
public interface CarRepository {
Optional<Car> findClosestCar(float range);
}
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();
}
}
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());
}
}
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));
}
}
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);
}
}
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)
[[..PingBack..]]
This article is curated as a part of #63rd Issue of Software Testing Notes Newsletter.
Thank you!