Comparison with other tools

Here's how Mirage compares to some other API mocking tools and approaches.

JSON Server

JSON Server is a popular tool for creating instant REST APIs in node.

The primary difference between Mirage and JSON Server is that Mirage is designed to mock out the exact shape of your production API, whereas JSON Server creates a specific API format based on its conventions and what you put in its database file. While there are some configuration options available, it can be difficult or impossible to use JSON Server to fully replicate your production API routes and payloads, meaning your application code would not be written and tested against a production-like environment.

Mirage's philosophy is to stay on the HTTP boundary and not affect your application code. Your production API is what drives your Mirage configuration, not the other way around.

Some other advantages Mirage has:

  • Mirage runs in the browser alongside your frontend code, so getting it running in your dev environment and using it to run tests on your CI servers is simpler – no extra processes to manage or reset.

  • Mirage supports full referential integrity via its ORM, and manages all your resources' foreign keys even as your app modifies data.

  • Mirage resets its state on every browser reload, which can be a double-edged sword but is often what you want for development and testing. (You can use localStorage if you'd like to add temporary persistence to Mirage.)

  • Mirage works with GraphQL, or any other API format.

JSON Server does have some advantages over Mirage:

  • It's a real server, so you get to use the network tab in the devtools and other HTTP tools like Paw or curl to send requests and inspect responses.

  • It's written in node and express, so you get the full power of the node ecosystem.

  • It has filtering and sorting implementations out of the box (though again, it's unlikely that they will match your production API).

MSW

MSW is a mocking library that can run in the browser or node.

While Mirage and MSW both run in the client alongside your frontend code, the main difference is that MSW uses a Service Worker to intercept network requests, whereas Mirage uses Pretender.js which works by monkey-patching window.fetch and window.XMLHttpRequest.

The main advantage of using a Service Worker is that requests are shown in the Network tab of the DevTools – they're real HTTP requests from the perspective of the browser. This makes the developer experience of inspecting responses from MSW closer to that of interacting with a real server-side API.

Some other advantages MSW has:

  • It can run in node, which means your mocks can be shared in a non-browser node environment. Mirage also runs in node, but only in a browser-like environment (i.e. in the presence of something like jsdom, which tools like Jest configure for you). At this time Mirage can not be used to mock out a React app's server-side API requests, for example.

  • It has first-party APIs to help you mock out GraphQL. (Mirage can also be used to mock out GraphQL, there are just currently no first-party APIs in the library to help you do it.)

The primary advantages Mirage has compared to MSW are similar to those it has over other libraries that only provide low-level interceptor functionality. MSW's main API is a route handler that gives you a request and lets you write a response. Mirage provides a similar route handler API that can be used in exactly the same way, but it also brings along a DB, support for relationships, factories that make creating different data scenarios easy, a serializer layer to help you match the shape of your JSON payloads to your production API, and a server instance that makes asserting against DB state and mutations easy within your tests.

After wiring up any interceptor (Mirage, MSW, Pretender, fetch-mock, etc.), implementing your API routes in a way that faithfully reproduces the behavior of your production API is typically where most of the complexity of your mocking code lives. So, any library that only provides interceptor functionality is leaving this work up to you. This is true for both GraphQL and REST APIs.

Some other advantages Mirage has:

  • Mirage is slightly easier to set up – just one import alongside any frontend code:

    import { createServer } from "miragejs"
    
    let server = createServer()
    server.get("/api/movies", () => ["Interstellar", "Inception"])

    MSW requires one additional step - you need to generate a Service Worker file in the public directory of your project, which you might also want to strip from production builds.

  • Mirage was built for testing and brings along concepts like development vs. testing data seeds, and the ability to create different data scenarios directly within your tests without having to rewrite route handlers.

MSW is a great library and the service worker concept might even make it into Mirage at some point, but mocking every endpoint by hand is tedious and hard to maintain over time. Having some conventions around your mocks and ensuring that they maintain the referential integrity of your data as your JavaScript app makes read and write requests is the reason Mirage was created in the first place.

Other low-level mocking libraries

There are low-level mocking tools like Pretender and FakeRest that are often used in isolation (usually tests) to define static mocks for specific endpoints. (Mirage actually uses Pretender under the hood as its inceptor!)

The biggest difference with tools like these and Mirage is that Mirage is designed to let you faithfully recreate your entire production API server, so that your app can interact with it in exactly the same way it would talk to the network in production. Mirage's data and serializer layers are what enable this. Mocking every endpoint by hand can be tedious and hard to keep updated, and is what actually what led to Mirage's existence in the first place.

GraphQL query mocking

When working in GraphQL it is common to mock at the query level, using packages like Apollo MockedProvider. This has a similar problem as low-level REST mocking libraries, and it can be quite difficult to maintain the referential integrity of your data when mocking multiple queries (i.e. your app creates a movie and then follows it up with a query to allMovies, but the new movie isn't there.)

These same sorts of pain points are also when led to the creation of Mirage, which fundamentally operates at the resource level rather than the query level.

Cypress route mocks

Cypress includes the cy.route() API which lets you stub out your backend during testing.

Cypress' network mocks are based on static fixtures (typically JSON files) and thus suffer from the same kinds of problems as other tools that don't use something like a database to ensure consistency of resources across CRUD operations.

Cypress also has an alias API that encourages you to explicitly wait on its stubs in a test:

cy.get("#autocomplete").type("Book")

// Wait for the request + response
cy.wait("@getSearch")

cy.get("#results").should("contain", "Book 1")

From Mirage's perspective, this test contains two distinct levels of abstraction that typically should not be mixed: first are the visible UI elements and user interactions, which are written from the actual end-user's perspective (things like type('Book') and (#results).should('contain', 'Book 1')), and second is the HTTP alias (wait('@getSearch')) which is an implementation detail of this page and not actually visible to the end user.

Mirage recommends writing UI tests at a single level of abstraction, usually focused on the real-world behavior of your end user. In the case of waiting for HTTP responses, it is best to have your test wait on some piece of UI that indicates whether the request is pending or fulfilled, rather than an implementation detail like the specific HTTP requests that happen to be powering the page. For this reason, we believe Cypress' alias API can lead to brittle tests that make your code harder to refactor.

These differences are just about Cypress' HTTP mocking layer though – overall, we're big fans of Cypress! And Mirage works great when used as the API mocking layer right alongside your Cypress testing code. In fact, we even have a Quickstart guide on getting Mirage set up in a Cypress app!

Other differences include the fact that Cypress' API mocks are typically not shared in development, which is a big part of Mirage's core value offering. Additionally, as of this writing, Cypress doesn't intercept fetch requests, whereas Mirage does.


If you are curious how Mirage compares to another tool not listed here, please feel free to ask by opening an issue!