Making API mocking easy with Mirage!

AswathPrabhu R
Engineering @ Chargebee
5 min readMay 31, 2022
Build your entire UI against a mock backend API

We use Mirage at Chargebee and it is the one deed that we did and in return it has filled two of our needs 😇. Our experience with Mirage is quite exciting and we can’t wait to share it here! Let’s go already!

What is Mirage? 🤔

Mirage helps frontend developers to configure a mock server for the backend APIs and the server is centralised for both development and the test suite.

It’s just not a plain old API mocking tool, but has all the batteries included that helps to build complete frontend features even if our APIs doesn’t exist. Let’s say you wanna build up a product prototype rapidly fast with minimal overhead, that’s completely possible with Mirage.

Life before Mirage at Chargebee 😅

Before implementing Mirage, we had quite an inflexible approach where for testing, we were copy-pasting chunks of static fixtures as return values for jest mock functions here and there and testing over them.

This approach was just covering a set of cases (mostly covering the happy path of the users) for us and handling dynamic scenarios was a headache where we were forced to do a lot of work.

For the development of the features without the backend dependency, we were using axios interceptors for mocking the APIs. We were just routing the network calls made in the application to a local node server that handled the endpoints.

This obviously was not living alongside the rest of the application and forced us to switch contexts. Also here again we were tracing the happy path mostly and were not dealing with the network states and latency.

What makes Mirage a better approach? 😎

Mirage runs alongside the rest of the application, no new server processes or terminal windows are needed. It has an in-memory database that ensures referential integrity between different data models. Additionally, there are concepts like,

Factory

Factory layer helps to organize the data-creation logic. Mock server can be quickly put into a different state during development and as well during testing. During development, the in-memory database can be seeded via factories on seeds hook to load some initial data.

Serializer

Serializing layer hooks into the state where route handlers process data models to return a response. It is responsible for the format of the data returned. There are some built-in serializers included and also an option to customize the default behavior.

Woo-hoo! Mock server can be centralized 😇

This is one of the very useful features where the mock server can be shared between the actual development workflow and the test suite. With factories, various dynamic scenarios of a component can be tested.

Enough of theory? Let’s get practical! 🥳

This is the sample application we’ll be using to get a gist of Mirage’s capabilities. It displays random quotes from Game of Thrones on every load. It also includes a route with a dynamic segment that denotes the number of quotes requested (for ex: `/10` would query 10 quotes via the API and render the results)

The application is hosted here.

In the below playground, you can find the main.js where we have the application’s routes config. We are also spinning up the Mirage server for development environment. Now, Mirage can intercept all the network requests triggered from the application and respond with mock response.

You might encounter limitations when running server in the preview window. If that’s the case click here to open the playground in a new tab. This is due to some limitations of cross-origin isolation policies.

The server config of the Mirage can be found here,

You might encounter limitations when running server in the preview window. If that’s the case click here to open the playground in a new tab. This is due to some limitations of cross-origin isolation policies.

We’ve set up the quotes factory. This can come in quite handy to test various dynamic scenarios which we will see later. You can find the routes intercepted under routes method.

If we are spinning up Mirage server on environments like development , usually we will be passing through(instead of mocking) all the APIs to the actual server. Mirage exposes passthrough method on the server instance for this exact functionality.

We can also avoid passing through during active development, for ex: while the API is not yet ready. We can just construct our factories according to the spec of the API and let the mirage server intercept all the requests by enabling routes on development environment too.

Once everything is fine and the API is ready, we could just disable routes on the development environment and pass through them, Now the requests will be passing through the network layers hitting the actual servers. Things should just work!

There shouldn’t be any rework required if we’ve setup our Mirage models exactly mirroring the actual backend. Definitely some can have questions like,

Is this all worth it?

Are we over engineering doing UI without backend?

Instead can we have mock data just as data properties in UI components?

The real power comes when the Mirage mock server is also shared by the test environment. Just have a look at this component test file in the below playground,

You might encounter limitations when running server in the preview window. If that’s the case click here to open the playground in a new tab. This is due to some limitations of cross-origin isolation policies.

The above file handles unit tests for the card component that renders the card view. See how quickly we are able to put the API response into different states and scenarios without copy pasting random mocks here and there! This is where the factories come into play.

To accomplish this, you can see that we’ve done nothing complex. We’ve just setup our quote factory to create required mock dynamically and added a route to intercept in a express style syntax! That’s it! Mirage also has batteries included to handle complex data relationships.

This kinda flexibility can’t be just achieved using data properties on UI components that hold mock data.

Some cons of using just data props for mocking can be,

  • Integrity with backend can’t be ensured
  • Data fetching, various network states and persistence can be very complex to handle
  • There is no straightforward way to dynamically generate responses.
  • It will only include user’s happy path.

On the other hand Mirage ticks all these boxes for us and we are efficiently able to handle all our cases here!

Do we have some alternatives? 🤤

Yes. MSW is a similar tool. It uses service worker under the hood to intercept network requests whereas Mirage just mocks out XMLHttpRequests and fetch API.

This is an advantage of MSW over Mirage as the requests are shown in the Network tab of the DevTools — they’re real HTTP requests from the perspective of the browser. This gives a great DX while doing actual development.

MSW’s mock server can also be used in test environment. The route handler gives a request and lets us write a response (interceptor functionality) but nothing more than that.

Mirage on the other hand provides a similar express style route handler API and also brings along an in-memory DB, support for relationships and factories that make creating different data scenarios easy.

We stuck with Mirage here as it came with batteries included for mocking in a way that faithfully reproduces the behavior of the production API that in turn helped us to test various dynamic scenarios and not just the happy path!

So that’s it. This is how beneficial Mirage is for us and we can’t wait to hear your experience with it in the comments. Also, shoot up anything you’ve, let’s keep the discussion on! 👋

--

--