Most mature Ember apps have an API mocking layer. Usually it is ember-cli-mirage. Often nobody on the team chose it. It came with the app.
This post is about deciding whether to keep it, and how to change it if you must. The default answer is to keep it.
Why the question comes up
Three things push teams to ask.
- A migration off Ember is planned, and mocks tied to Mirage will not travel to the new framework.
- Mirage's ORM and factories have drifted from the real API. Tests pass against data the server would never send.
- An upgrade surfaced a version conflict in the mocking addon or its dependencies.
None of these is a reason on its own. Check which one you actually have.
What each tool is
Mirage runs inside the Ember build. It intercepts requests in the browser and answers from an in-memory database with models, factories, and serializers. It knows Ember conventions.
Mock Service Worker (MSW) intercepts at the network layer. In the browser it uses a service worker. In Node it patches request APIs. Handlers are plain functions that return responses. It has no ORM and no Ember dependency.
The real difference: Mirage is a fake backend. MSW is a set of canned responses. Fake backends are powerful and costly to keep faithful. Canned responses are simple and easy to let rot.
Keep Mirage when
- Tests rely heavily on factories and relationships, and they are readable.
- The team knows it, and it is not blocking an upgrade.
- You will stay on Ember for the next several years.
Changing a working mock layer is cost with no user-visible return. Put that time into the tests that fail intermittently instead.
Move to MSW when
- You plan to share mocks with a React, Vue, or Svelte app during a migration.
- You want the same handlers in unit tests, browser tests, and local development.
- The Mirage addon blocks a build or Embroider change you need.
The shared-handler case is the strongest. During a strangler migration, both apps call the same API. One set of handlers means one place to fix drift.
Migrating in order
Do not convert everything at once. The app must stay shippable.
- Inventory. List every Mirage route, factory, and scenario. Count which tests use each. Many routes have no test that uses them.
- Write a contract check first. Compare mock shapes against the real API, using an OpenAPI schema if you have one, or recorded responses if you do not. Fix drift while still on Mirage. This is where most of the value is.
- Run both side by side. Add MSW for one feature area. Leave Mirage handling the rest. Check that the two do not both answer the same request. Set unhandled-request behavior to error, not warn, so gaps show up.
- Convert by test module. Pick the modules with the fewest factory relationships first. Replace factory calls with plain fixture builders in a shared folder.
- Handle relationships deliberately. This is the hard part. Mirage resolves includes and associations for you. In MSW you write that logic or flatten the fixtures. Prefer flat fixtures per test. They are longer and clearer.
- Remove Mirage last. Delete the addon, its config, and its scenarios only when no test imports it. Do this in its own pull request.
Tradeoffs to expect
- Tests get more verbose. Each one states its own data.
- Local development without a backend needs a decision. MSW can run in the browser for this, but you must set it up and keep it out of production bundles.
- Check current MSW and Ember setup guidance before starting. Test-setup details change between versions, and your Ember version matters.
When to do nothing
If your mocks are accurate, your suite is green, and no migration is planned, leave Mirage alone. A mocking layer is not where Ember apps get into trouble. Addons, build tooling, and staffing are.
If you are unsure which case you are in, an app assessment covers test health and mocking as part of the review. It ends with an effort range, not a pitch.