Introduction to Stubs

After we have finished this lesson, we:

  • Know what a stub is.
  • Understand how a stub works.
  • Understand when we should use stubs.

What Is a Stub?

A stub is an object that is used to replace an external dependency of the system under test. Every stub must fulfill these requirements:

  • A stub must provide the same API as the replaced external dependency. This means that if the external dependency is a class, our stub must extend it and override all methods. On the other hand, if the replaced dependency implements an interface, our stub must implement the same interface.
  • We must be able to configure the response that is returned when a method of a stub is invoked. This means that we can configure the stub to either return an object or throw an exception.
  • A stub returns the configured response every time when its method is invoked.
  • A stub provides no way to verify interactions that happen between the system under test and the stub in question.

Now we will demonstrate these rules by creating a simple stub. Let’s start by taking a look at the system under test.

Introduction to System Under Test

The system under test has a single dependency called TaskFinder. This interface declares one method called findByid() that takes the id of the requested task as a method parameter and returns the found Task object.

The source code of the TaskFinder interface looks as follows:

1public interface TaskFinder {
2
3    Task findById(Long id);
4}

Let’s find out how we can create a simple TaskFinder stub.

Creating a Simple Stub

We can create a stub that can replace the real TaskFinder object by following these steps:

First, we have to create a public and final class that implements the TaskFinder interface. It is a good idea to mark our stub class as final because it prevents developers from extending it.

The source code of our stub class looks as follows:

1public final class TaskFinderStub implements TaskFinder {
2
3}

When I name my stub classes, I like to append the string: ‘Stub’ to the type of the “real” object. Because our stub class is used to replace TaskFinder objects, its name should be: TaskFinderStub.

Also, this example assumes that we need to use this stub class from multiple test classes that are not necessarily in the same package as the stub class. However, if this is not the case, we should limit the visibility of our stub class.

Second, we have to configure the returned Task object. We can do this by following these steps:

  1. Add a private and final Task field into our stub class.
  2. Set the value of our new field by using constructor injection.

After we have added the code that gives us the possibility to configure the returned Task object, the source code of our stub class looks as follows:

1public final class TaskFinderStub implements TaskFinder {
2
3    private final Task found;
4
5    public TaskFinderStub(Task found) {
6        this.found = found;
7    }
8}

Third, we have to implement the findById() method of the TaskFinder interface. Our implementation must return the Task object that is stored in the Task field.

After we have implemented the findById() method, the source code of our test class looks as follows:

 1public final class TaskFinderStub implements TaskFinder {
 2
 3    private final Task found;
 4
 5    public TaskFinderStub(Task found) {
 6        this.found = found;
 7    }
 8
 9    @Override
10    public Task findById(Long id) {
11        return found;
12    }
13}

We have now created a simple stub. Next we will write a test method that demonstrates how our stub works.

Using Our Stub

If we want to use a stub in our tests, we have to remember that stubs are not stateless. This means that we have to create a new stub before a test method is invoked. We can do this by adding a setup method into our test class and creating our stub in that method.

After we have added this setup method into our test class, its source code looks as follows:

 1import org.junit.Before;
 2
 3public class TaskFinderStubTest {
 4
 5    private static final Long TASK_ID = 99L;
 6
 7    private Task found;
 8    private TaskFinder taskFinder;
 9
10    @Before
11    public void createStub() {
12        found = new TaskBuilder()
13                .withId(TASK_ID)
14                .build();
15
16        taskFinder = new TaskFinderStub(found);
17    }
18}

We can now write a test method that uses our stub by following these steps:

  1. Invoke the findById() method of the TaskFinder interface.
  2. Verify that the TaskFinderStub object returns the expected Task object.

The source code of our test method looks as follows:

 1import org.junit.Before;
 2import org.junit.Test;
 3
 4import static org.assertj.core.api.Assertions.assertThat;
 5
 6public class TaskFinderStubTest {
 7
 8    private static final Long TASK_ID = 99L;
 9
10    private Task found;
11    private TaskFinder taskFinder;
12
13    @Before
14    public void createStub() {
15        found = new TaskBuilder()
16                .withId(TASK_ID)
17                .build();
18
19        taskFinder = new TaskFinderStub(found);
20    }
21
22    @Test
23    public void findById_ShouldReturnConfiguredTask() {
24        Task returned = taskFinder.findById(TASK_ID);
25        assertThat(returned).isSameAs(found);
26    }
27}

This example has demonstrated that a stub is a test double which:

  • Allows us to configure the response that is returned when its method is invoked.
  • Returns the same response every time when its method is invoked.
  • Provides no way to verify the interactions that happen between the system under test and the stub in question.

Also, these principles are valid even if we create our stubs by using a mocking library. Let’s move on and identify the situations when using stubs make sense.

When Should We Use Stubs?

Typically a system under test has two kinds of external dependencies:

  1. Dependencies which query information that is used by the system under test. These dependencies can read information from the used database, fetch it from an external API, and so on.
  2. Dependencies which trigger an action that has a “side effect”. These dependencies can save information to the used database, send an HTTP request to an external API, trigger an event, and so on.

Because we cannot verify that an interaction happened between the system under test and a stub, we should use stubs in situations where we don’t have to verify that the system under test actually invoked a method of a stub.

If we compare this argument with the external dependencies of the system under test, we notice that:

  • If the dependency is only a datasource, we don’t have to verify the interactions that happen between the system under test and the external dependency, because we can use assertions for verifying that the system under test is working as expected.
  • If the dependency triggers an action that has a “side effect”, we have to verify that the system under test either invoked the correct method of the external dependency or did not invoke it. If we don’t do this, we cannot verify that the system under test is working as expected.

In other words, we should replace an external dependency of the system under test with a stub if the external dependency is only a datasource.

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us four things:

  • A stub is an object that replaces an external dependency of the system under test.
  • A stub returns the configured response every time when its method is invoked.
  • A stub provides no way to verify interactions that happen between the system under test and the stub in question.
  • We should replace an external dependency of the system under test with a stub if the external dependency is only a datasource

Get the source code from Github