Mocking Third Party Services

While extensive mocking is one of the hallmarks of good unit testing practices, it can sometimes be desirable to use mocking in our integration tests as well. Why? The systems our integration tests rely on might,

  • Create real-world consequences (e.g. bill a customer, send emails)
  • Perform slow running or even manual processes
  • Have difficult to produce edge cases
  • Require advanced authentication methods not available to test environments
  • Not have a sandbox environment
  • Not be in our control (i.e. a third-party service owned by another team/company)
  • Fail our tests when the third-party system is unavailable (sometimes desirable, sometimes not)

So how can we still have integration tests for our system without bumping to these issues? We can mock the third-party system. Here are some helpful tips for how to do this.

Do You Really Need To?

It's worth pointing our that before you go down this path, you'll want to make sure you know what you're getting into. "Code-level mocks" that are created for unit tests are usually pretty simple. "Service-level mocks" that are created for integration tests can get pretty complicated. At some point, if you're not careful, your mock service could end up trying to implement all of the functionality of the third-party system.

Before going down this road see if you can get access to a sandbox/test environment. Even if they don't advertise having one, it's worth asking. Using a sandbox/test environment for the service means that you may be able to avoid the work of creating a mock service altogether.

Test environments can also have the advantage of letting you preview upcoming functionality, giving your team more time to fix issues before they land in production.

Refactor if Needed

Build a service layer with interfaces. If you have Clean Code, then you already know the important of building a service layer with interfaces. Interfaces enable you to use dependency injection to change the underlying implementation your code relies on at run-time. The interfaces also provide a form of executable documentation. What better way to know what functionality in the third-party system you use and rely on than having all of your code access the system through a well-defined interface.

This is what a "code-level mock" typically looks like,

public interface PaymentProcessor {
 public createAccount(String companyName);
}

class StripeProcessor implements PaymentProcessor {
 public createAccount(String companyName) {
   stripeApi.createAccount(...);
 }
}

class MockProcessor implements PaymentProcessor {
 public createAccount(String companyName) {
   // ...  
 }
}

In a "service-level mock", you can replace an entire service with another service that behaves the same under your test scenarios." So for example, instead of making a REST call to "https://myserviceprovider.com" you can make a REST call to "https://mydomain.com/mockapi".
You might do something like,

PUT /accounts
{"accountName":"Test Account"}

And then when your test calls getAccounts, Test Account is returned.

It's a good idea to use both dependency injection and service-level mocking. This enables your tests to leverage different functionality for testing. You can fire up your application and run everything under the mocked service configuration and then you can fire it up using the real services.

By the way, if you are dealing with a legacy system with real dependencies scattered everywhere, I highly recommend reading Working Effectively with Legacy Code. It has some pretty good content on effective ways to introduce testing and refactoring into code bases that weren't built with testing in mind.

Understanding the Contract

Whenever two services communicate, there is a contract involved. That contract may be explicitly stated or implicitly understood. Make sure the contract between your service and the third-party service is stable and well-understood. Your mocked service is going to implement this contract. From that point forward, your tests will assume the contract is the requirement. That means, if the third party service violates that contract, your tests will pass when they should be failing.

It is critical that you have a process in place for updating the mocked service when the third-party system changes its contract. Otherwise your tests will only detect when you're breaking the contract, not detect when the system is broken due to contract changes!

Smoke Test with the Real Service

One of the most common failures that happens in production even in the most well-tested environments is due to configuration differences when going to production. The configuration is where you update the variables that point to test environments to point to prod environments.

It's important to run both tests that rely on the mocked service (run fast, reliable, test failure modes) as well as tests that verify the real service is wired up correctly. This will catch things like "I forgot to add the prod config variable" or "I forgot to update the test config to the prod config".

The production smoke tests shouldn't try to test functionality. They should just confirm that the real service is there and that there is connectivity.

Tools That Can Help

There are some tools and services available that are available for service-level mocking.

  • MockServer - Service for simulating HTTP-based APIs. Can also proxy end points giving flexibility.
  • SendGrid - Send and receive test emails.
  • Testery - Testery doesn't do service-level mocking, but does enable running end-to-end tests with different configurations. So you can run some tests against the mocked server and others against the real service.
  • WireMock - Set up a service for simulating HTTP-based APIs. Can host on MockLab.
  • Mock Service Worker - A good option for mocking REST services for UI tests against React apps.

Putting it All Together

If you've read our other content, you'll know that we're big proponents of running the right tests in the right place at the right time (something Testery Cloud does really well).

Relying solely on unit tests and integration tests is problematic. So is relying solely on end-to-end testing with the real services.

The wise approach is to blend different testing techniques and use the right technique depending on the requirement being tested. Service-level mocking can be a really important technique in your tool belt for running tests that rely on third-party services.