How to implement mock objects for simpler testing
Test mocking can serve as a valuable asset in a developer's toolbelt. Here's how to implement mock objects within testing processes and some potential pitfalls to be aware of.
Test mocking refers to when developers replace or modify code in a way that simply simulates or mocks what the production code would do. When used properly, mock objects can help teams write more reliable and efficient code tests.
Mock objects are a type of test double. They replace production objects to make for easier and more reliable testing practices.
If developers want to use mock objects as part of their testing process, they need to be aware of potential pitfalls that can harm their applications' code. Let's examine what mock objects simulate in code and how to properly use them to create more specific tests.
What do mock objects simulate?
Test mocking allows testers to narrow test scopes and focus on specific parts of the codebase. Here are two examples of how testers use mock objects:
Simulate a database connection. A developer can use test mocks to avoid reaching out to a database in production. In this scenario, if an application needs to reach a live database, perform some complex translation and then return a result to the user, a developer can mock those processes with a hard-coded response and avoid the live database.
When simulating the database call, the mock object would provide the complex translation function with a set input -- without passing through other code that would make a network connection to the database.
The benefit of test mocking in this example is that it can eliminate unnecessary variables – such as rate limits from third-party integrations or external-network connectivity -- to create more reliable and focused tests.
Onerous test setup requirements. Let's say a developer wants to deploy a new service and needs to understand how the application would perform under load. The tester begins with a recorded JMeter script, but quickly sees 5xx response codes because the service reported rate-limiting errors with the application's third-party integrations. If the application is in test mode, these third-party services won't allow the hundreds of requests per second that load tests generate.
Without test mocking, developers would probably resort to paying for a production tier to evaluate these errors and not be limited by the third-party integrations. Testers could instead mock these third-party integrations. This approach enables the tester to focus the load on the application's code and remove the bottleneck of other services they don't have control over.
Test mocking is useful for more than just avoiding rate limits or simulating connections to databases. For example, imagine a scenario where an application requires a multi-factor authentication (MFA) code to log in. It can be a difficult to generate this MFA code in a timely and reliable manner during testing, and it doesn't need to be tested in each test case that uses the MFA code path. Instead, testers can mock something like this by writing a second implementation of an application's MFA feature where either the test bypasses it entirely or the feature accepts any combination of numbers. Then, the team can set up this dummy feature that's supported by an environment variable or some other switch that's provided to the application during runtime.
When running tests in development stages, testers can flip on the dummy MFA implementation to use the environment variable. However, they need to remember that it's also important to test the MFA feature when it's fully enabled before the application is finally deployed to production. When teams write complicated workarounds for difficult-to-test features like this MFA example, it slows down tests and can easily introduce flakiness. If teams implement mock objects properly, they can reduce test result variability. This helps create repeatable and helpful results and allows test cases to independently test features that they're intended to validate.
What are some potential pitfalls of test mocking?
While mocking services can help eliminate levels of testing variability and complexity, testers need to be aware of the accuracy of mock implementations -- which is indeed something to watch out for.
Mock objects should act as similar as possible to their production counterparts. If they don't, testers end up writing test cases that won't serve much of a purpose. Instead, they'll see results rooted in bad information. Without realistically mocked objects, the test cases would represent a scenario that won't occur in production -- and, in turn, limit the usefulness of the test results.