Mocking and Its Importance in Integration and E2E Testing
Learn about mocking, its role in integration and E2E testing, and how to mock an API effectively using tools like WireMock for reliable results.
Join the DZone community and get the full member experience.
Join For FreeIn the software development lifecycle (SDLC), testing is one of the important stages where we ensure that the application works as expected and meets end-user requirements. Among the various techniques that we use for testing, mocking plays a crucial role in testing different components of a system, especially when the external services that the application is dependent on are not yet ready or deployed.
With that being said, let’s try to understand what mocking is and how it helps in integration testing and end-to-end (E2E) testing.
What Is Mocking?
Mocking is the process of simulating the behavior of real objects or services that an application interacts with. In other words, when you mock something, you are creating a fake version of the real-world entity that behaves like the real thing but in a controlled way.
For example, imagine you are building an e-commerce application. The application might be dependent on a payment gateway to process the payments. However, during testing, it might not be feasible to use the actual payment gateway service due to various factors like costs, service unavailability, not being able to control the response, etc. Here comes the concept of mocking, which we can use to test our application in a controllable way. Mocks can be used to replace dependencies (API, databases, etc.) and test our application in isolation.
The Importance of Mocking
- Faster tests: Most of the time, when we interact with external services, tests usually tend to be either flaky or long-running due to external service either being unavailable or taking a longer time to respond. However, when we consider mocks, they are usually fast and reliable, which helps in faster execution of tests.
- Ability to test edge cases: When we use mocks, we have complete control over the response that a service can return. This is helpful when we want to test edge cases like exception scenarios, time out, errors, etc.,
- Isolation: With mocking, we can test specific functionality in an isolated way. For instance, if the application relies on a database, we can mock the database response in case we have challenges in setting up specific test data.
- Eliminate dependencies: If the application depends on a lot of external services that can make our tests unreliable and flaky, we can use mocks, which helps make our tests reliable.
How to Mock an API?
Now, let’s look at an example of how to mock an API call. For illustration purposes, we will use Java, Maven, Junit4, and Wiremock.
1. Add WireMock as a dependency to your project:
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>3.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.26.3</version>
<scope>test</scope>
</dependency>
2. Add the WireMock rule:
import static com.github.tomakehurst.wiremock.client.WireMock.*;
3. Set up WireMock:
@Rule
public WireMockRule wireMockRule = new WireMockRule(8089); // No-args constructor defaults to port 8080
4. Mock an API response:
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
...
@Test
public void exampleTest() {
// Setup the WireMock mapping stub for the test
stubFor(post("/my/resource")
.withHeader("Content-Type", containing("xml"))
.willReturn(ok()
.withHeader("Content-Type", "text/xml")
.withBody("<response>SUCCESS</response>")));
// Setup HTTP POST request (with HTTP Client embedded in Java 11+)
final HttpClient client = HttpClient.newBuilder().build();
final HttpRequest request = HttpRequest.newBuilder()
.uri(wiremockServer.url("/my/resource"))
.header("Content-Type", "text/xml")
.POST().build();
// Send the request and receive the response
final HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
// Verify the response (with AssertJ)
assertThat(response.statusCode()).as("Wrong response status code").isEqualTo(200);
assertThat(response.body()).as("Wrong response body").contains("<response>SUCCESS</response>");
}
Best Practices
- Use Mocks only when required: Mocks help isolate the external services and test the application in a controlled way. However, overusing the mock can cause bugs in production if not tested with real services in staging environments.
- Mock External Services only: Only external services should be mocked and not the business logic.
- Always Update Mocks with the latest system contracts: Whenever there is a change in the real service contract/response, make sure the mock is also updated accordingly. Otherwise, we might be testing inaccurately.
Conclusion
Mocking comes in very handy when it comes to integration and end-to-end testing. Specifically, in tight deadlines, when the external services code changes are not ready for testing in the staging environments, mocking helps to get started with testing early and discover potential bugs in the application. However, we always need to ensure that the application is tested with real service before deploying to production.
Opinions expressed by DZone contributors are their own.
Comments