This article is written for part of the Software Engineering Project course in University of Indonesia. Any information put here may or may not be complete and should not be used as your only guideline.
In our previous article, we have talked about Test-Driven Development and it’s weirdness. We extensively covered the mantras of TDD, the jokes when you don’t use testing, and reveled into how we actually unit test a code. Seriously, it’s a great article, you should check it out (shameless self-promotion).
But then, we encountered a problem: “How do we isolate parts of the code to test the smallest functionality of an application?”. Fear not, because we are mocking the code.
But first, let’s dive in to the flavor of insults that the code has for us!
In testing, it’s best to use interactive test objects that appear to function the same as their production equivalents. These test objects are actually quite simple, and useful only for testing.
This approach reduces complexity. It’s much easier and faster to reach a point of verifying code independently from the rest of the system. A test double is the name for such test objects. Although there are many types of test doubles, the term mock is commonly used in reference to all of these types. However, it’s important to avoid misunderstanding and improper mixing of test double methods.
A fake is an object that has some level of internal function that is somewhat less than the corresponding production object. Commonly, a fake results from taking one or more shortcuts on a copy of production code.
An example of such a shortcut is an in-memory repository or data access object. A fake will not actually engage any database at all. Instead, it will employ a simple collection scheme to store test data. The main benefit here is the ability to perform an integration test of services. It’s unnecessary to initiate a database connection and submit time-consuming requests. The effort spent to create the fake will be repaid in simplicity and performance.
Stub is an object that holds predefined data and uses it to answer calls during tests. It is used when we cannot or don’t want to involve objects that would answer with real data or have undesirable side effects.
An example can be an object that needs to grab some data from the database to respond to a method call. Instead of the real object, we introduced a stub and defined what data should be returned.
A mock is an object that registers any call it receives. A mock is most useful when there is to be no return value — and no practicable means for checking change of state.
One broadly representative example of a need for a mock is a call to an email messaging service. It’s unnecessary to send an email message each time a test is run. Moreover, it may not be easy — or necessary — to verify the content of the email message. However, it is to verify that interactive functionality is working properly. In our example, this means simply verifying that the e-mail messaging service is called.
To Mock or Not to Mock?
A unit test should test a single code path through a single method. When the execution of a method passes outside of that method, into another object, and back again, you have a dependency.
When you test that code path with the actual dependency, you are not unit testing; you are integration testing. While that’s good and necessary, it is not unit testing.
As such, we use mocks when we don’t want to invoke dependencies. Using mocks may seem innocent, but there are some caveats:
Mocks can lead to violations of the DRY principle
DRY is stated as:
“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system”
So, this isn’t just about code duplication — it is about knowledge duplication. When you use a mock you are in violation of this principle because the knowledge of how the components in the system interact is defined in two places: once in the production code and once in the tests. Any change to those interactions has to be made in two places.
Mocks make refactoring harder
Refactoring as “the process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure”. A fair assumption when practicing refactoring is that unit tests should pass before and after any given refactoring that we do. This is the essence of the RED-GREEN-REFACTOR mantra that I’ve talked: we only attempt a refactoring when we are in a green state, and we expect to still be in a green state after the refactoring.
But, mocks break this assumption. Because with mocks, our tests do NOT merely test the “external behavior of the code”, but also test the internal workings of the code under test — specifically what other modules are being used and how.
Mocking also inhibits refactoring because some refactoring tools don’t cleanly refactor code that uses mocks (e.g. renaming a method may not catch method names that are represented as strings, as in some mocking frameworks).
Mocks reduce design simplicity
As explained in the above example, the use of mocks makes the system harder to change. When the system is harder to change, we become reluctant to refactor, and the design slowly deteriorates over time.
Mocking is a sign of a design with too many dependencies. Mocking basically allows us to treat highly dependent code as if it were not dependent, without having to change the design.
Great! I’ve acknowledged the cons of Mocking, but how do I actually Mock stuff?
In this example, I’m using a Mocking tool for Java 1.8 called Mockito. Spring tests module already included it in our dependencies, so we shouldn’t worry! To add Spring Test starter module, just add this line to your Gradle project:
Once the dependency is added, you will be prompted to sync the project. Do that and you should be ready to implement our library in further steps.
Creating our Class to Test
In this example, I will be using my code from my Software Engineering Project:
Looks simple right? The piece of code calls
/token endpoint of the Google OAuth API to get JWT Tokens. By the looks of it, the code depends on
authTemplate and Google’s implementation of
/token endpoint to work.
However, we cannot rely on actually hitting Google’s endpoint to unit test our application. It wouldn’t be “the smallest functionality” to our code. The smallest functionality would be generating headers and request body, and also hitting the correct endpoint and method.
In the test class, I
@Autowired actual singleton instances of the
And then, I used a tool from Spring Testing module that actually mocks
MockRestServiceServer . You can read about it more here.
.expect() and .andExpect() is a builder function to mock the REST API call. As you can see, the
MockRestServiceServer is expecting
service.getToken(“test”) to call the
/token endpoint, with content containing multiple strings that is on the request body.
By then it stubs the response with
TokenResponse response that I’ve prepared before. Theoretically,
TokenResponse ans and
TokenResponse response should have the same values, which are verified by
Mocking is a weird concept, I know. However, it’s a necessary weirdness. It provides powerful tools that completely ignores dependencies upon testing. However, you should be aware of its caveats, as overly mocking a test would result in an unmaintainable code.
So, given these drawbacks to mocks, how do we decide when to use them? I use the following guidelines in my own code:
- Only use a mock when testing things that have dependency which you cannot confirm.
- If I truly need a test double, I go to the highest level in the class hierarchy diagram above that will get the job done. In other words, don’t use a mock if a stub will do. This is because the lower you go in the class hierarchy of test doubles, the more knowledge duplication you are creating.