While testing a software application or a product, we might create objects or components that are not real. We need them to simulate the response to everyday scenarios and edge cases. We typically refer to this process as creating Fakes or Test doubles. We can also refer to Mock or Stub when we adopt testing libraries or frameworks to automate the software testing process.
In this tutorial, we’ll see how we use fictitious data such as Fake, Mock, and Stub.
2. Why Do We Need Fake, Mock, and Stub
There are many types of tests, for example, unit or integration tests. However, we can narrow the test sequence into a few steps:
Whenever we build tests, we should create scenarios from the business requirements. Then, we can simulate all the different cases and assert that we get the expected result against the actual outcome of a test. Commons approaches are TDD for code coverage or BDD to explain the application behavior to a user.
However, we need to feed our test suites with some input. It can be time-consuming to get authentic data. Furthermore, there could be issues like, for example, adding coverage to services that are not available in a test instance.
Let’s look at the type of data and techniques we can apply in our testing environment.
We can group in the Fake category whatever data, object, or implementation does not correspond to something we can find in a production environment. So regardless of a specific technique we might use, a Fake is something we use to pretend we behave like in the actual application. It is usually just a copy of a class or a module with pre-computed responses.
3.1. Fake Objects
Typically, we can refer to a Fake as a copy or clone of a class object with limited functionalities. For example, let’s say we have a UserRepository class implementing an interface:
We might need our repository to return a fake value. In this case, a Fake will provide a custom response, for example, a static or in-memory value instead of the actual computation.
3.2. More Than a Fake
There are downsides to this approach. For example, we can’t directly fake an interface. Thus, we need to implement a fake class every time. Also, we need to save objects to our in-memory map to get our tests working beforehand. This approach might not be flexible. Let’s see how programming languages and the libraries we use can make a Mock or a Stub a more suitable replacement for a Fake.
We refer to a Mock as an object representing reality, although with limited functionalities. Furthermore, before starting a test, we set expectations on a Mock and instruct it to be deterministic when something happens. This way, we can enumerate all the different states we run into during a specific flow of an application and build a series of tests.
We can think of a Mock as a puppet or a marionette we can manipulate to behave the way we need under certain conditions.
4.1. Mock Objects
We can mock a class or an interface. No actual implementation of the mock will be called. It does not even matter if a mock interface is not implemented yet. Notice that Mock class properties will return a default value. For example, for a string or a list, we will get null or false for a boolean.
4.2. Mock and Verify Interactions
Let’s make an example of mock usage. Suppose we have the AuthenticationService. This service depends on the UserRepository using Dependency Inversion. Let’s see how we can mock the repository:
Mocking will go by default through the hierarchy of dependency injection of objects. If not otherwise specified, we’ll get a mock instance of the UserReposiory and a database connection mock. Notice that for the findUserByName(), the return method will default to an empty User, although we can add our custom response.
For Unit Tests and isolating different units of work of a class, mocking saves us time in creating a database connection. We can have an interface mock without even having an actual implementation. Therefore, we can assert that our business logic works regardless of the database implementation.
Finally, another relevant feature of a Mock is the possibility of verifying if a required interaction happens.
Let’s put this all together in a diagram:
This flow describes how the service works regardless of the implementation. This should cover all possible cases and will convert to specific tests. If the logic changes, we also expect our test to fail accordingly.
4.3. Mock in Action
In real life, a Mock is managed by specific libraries or frameworks. A developer will usually rely on libraries to plug into the application. These libraries will build the test infrastructure and manage the lifecycle of the objects we want to mock. A developer will be responsible for transforming business requirements into test coverage. Let’s look at some popular mock libraries/frameworks:
A Stub is a lightweight version of a Mock. It might have the structure of a Mock. However, it cannot record interactions or return multiple values. Therefore, we should use a Stub when we need a fake object but without the flexibility of a Mock.
5.1. Stub Objects
For example, suppose we build an application with multiple modules. However, a module is not ready yet. We might want to use a simplified version that temporarily provides canned responses. Let’s see how we could stub an AuthenticationService:
We see we return a default User whenever we try to authenticate. This will help us to go through our application flow until our actual service is ready. This can be applied in testing or as a temporary replacement while building a module.
Notice we can describe a Stub as a specific use case of a Mock. Specifically, when it systematically returns a default, we don’t need to verify interactions. Notice also this approach is similar to faking a repository, although we can usually stub an interface.
5.2. Stub in Action
A Stub is often considered by many libraries or frameworks similar to a Mock, so from a software testing perspective, it is sometimes interchangeable with a Mock.
6. How Fake, Mock, and Stub Differ
Let’s summarize how Fake, Mock, and Stub differ:
In this tutorial, we described what a Fake is and why we need data or objects that are not real. We’ve also seen a Fake in action using a Mock or a Stub.
We saw how a Mock has more flexibility than a Stub and why it has better usage. Nonetheless, we’ve also noticed how we can use Fake, Mock, or Stub with the same meaning in the common language.