Łukasz Makuch

Łukasz Makuch

What a decent API mocking tool looks like to me

When I worked as a back-end developer, my tasks often included integrating our systems with third-party services. All I needed to do was to communicate with some other server over HTTP(S) and consume the responses.

Now, when my day to day job is being a front-end developer, I often need to fetch and update data on the back-end. All I need to do is to communicate with the server over HTTP(S) and consume the responses.

As you can see, when it comes to consuming APIs, the back end and front end world are pretty similar. It's all about sending requests and receiving responses. But it doesn't mean it's not challenging.

Setting our app to communicate with the real API may be a good way to start, but things get tricky pretty fast. We may soon want to handle not only the happy path but also all the loading and various error states. Because we cannot control the external service, even a manual QA tester may struggle a lot or be unable to reproduce some of these cases. Having reliable, reproducible automated tests is simply impossible.

The will of building robust solutions made me look for a tool that would allow me to create test doubles of APIs. That way I could not only test all the cases that are usually hard to reproduce, but also automate that process.

So, what makes a decent mocking tool?

One of the aspects I was looking for was the scalability of mocks. I didn't want the shortcomings of a mocking utility to ever force me to switch to a totally different tool in the middle of the development process.

Another thing I care about is actually running the code I write. The API specification may be thought as of a contract and assume that it's what the third party is going to deliver. That's why it's relatively safe to mock the API and test our code against that mocked API. However, mocking any sort of a higher-order API client is rather risky. We may ensure that our app communicates with that mocked client properly, but in the end, it's nothing but yet another piece of code we wrote. There's still no way to tell whether it sends the right requests and correctly reacts to the responses it gets.

Then, only the most trivial applications just read data and never update it. Even our beloved To-Do app alters the current list of items. The same request sent twice may produce two different responses and it may depend on a request issued in the meantime. It's a clear sign that a good mocking utility should enable us to mimic stateful behavior. Some test scenarios, especially those involving many components, may fire multiple HTTP requests. If that's how the app works when the users interact with it, then it's totally fine. Modifying the system under the test to fire fewer requests by swapping some internal components for test doubles makes it drift away from what the users actually use, what decreases the real-world test coverage and increases the risk of making wrong assumptions about how things actually work. On the other hand, mocking many endpoints for every test may be tedious. The good thing is that it doesn't have to. Similarly to how we can reuse the implementation of a button, we should also be able to write a mock once and then reuse it in multiple test cases. That way the coverage is high, test-related changes to the app are kept low, and the mocking process is not painful.

It's better to have slow tests instead of no tests, no doubt about that, but it's even better to have fast tests. And that becomes a challenge when asynchronous communication is involved. Some responses may need to be delayed to let the test code make assertions about how does the app behave while loading data. It's then important that they don't slow down all the other tests. To achieve good performance, we should be able to run both the test code and the mock code in parallel.

And last but not least, I wanted a platform-agnostic tool. I don't want to have to learn a new mocking tool just to test an app written in a different language.

I didn't find a tool matching these criteria. So I built one. And today I'd like to share it with you.

It's called Endpoint Imposter and it's a Node.js app.

You define your mocks in a JavaScript file.

module.exports = [
  {
    request: { path: '/todos' },
    response: { json: ['a', 'b', 'c'] },
  }
]

Then, assuming you have npm installed, you run one command in your terminal.

npx endpoint-imposter --port 3000 --mocks ~/my-mocks.js

And now you have a mocked server your system under the test can communicate with!

$ curl http://localhost:3000/sessionId/todos     
["a","b","c"]%

At first glance, it doesn't look much different from other popular solutions. But as soon as your tests grow, you'll notice that it scales pretty well. You can use it to mock your whole back end, including requests modifying data. All that without the need of writing a single function to handle the state. And because mocks are nothing but a JavaScript module, you can reuse them like any other module. Are you like me, that is a bit impatient when waiting for the tests to finish, but still willing to build robust apps? Then you may want to check out Endpoint Imposter's parallel sessions and the manual response release mechanism.

You can find more information in the documentation of Endpoint Imposter.