Mirage

Mocking server? WHY?
- doesn't consume resources
- doesn't depend on infrastructure
- easy to simulate special cases
- it's fast
Mirage
- mocking HTTP requests
- originally part of EmberJS
- express-like API
- built-in DB
- can be used with React, Cypress, Angular, Vanilla.js...
Tests in Ember
- Acceptance tests (e2e)
- Integration tests
- Unit tests
+ local development
How does it work?
- runs on the client ‼️
- monkey patches fetch and XHRHttpRequest
- uses Pretender
new Server({
routes() {
this.namespace = "api"
this.get('/hello-world', function() {
return { hello: 'world' };
})
}
})
Database
- manages id's for you
- easy to use API
- ORM
db.loadData({
movies: [
{ title: "Interstellar" },
{ title: "Inception" },
{ title: "Dunkirk" },
],
})
db.movies.insert({ title: "The Dark Knight" })
db.movies.findBy({ title: "Introduction" })
Schema
- relations using foreign keys
- based on Ember-data
new Server({
models: {
user: Model,
post: Model.extend({
author: belongsTo("user"),
})
},
fixtures: {
users: [{ id: "1", name: "Chris Garrett" }],
posts: [{ id: "1", authorId: "1", title: "Coming Soon" }],
}
}
Populating data
- fixtures
- typically in fixtures folder
new Server({
models: {
user: Model,
post: Model.extend({
author: belongsTo("user"),
})
},
fixtures: {
users: [{ id: "1", name: "Chris Garrett" }],
posts: [{ id: "1", authorId: "1", title: "Coming Soon" }],
}
}
Populating data
- factory methods
new Server({
models: {
movie: Model,
},
factories: {
movie: Factory.extend({
title(id) {
return `Movie ${id}`
},
}),
},
seeds(server) {
server.create("movie")
},
})
Routes
Routes
- express-like
new Server({
routes() {
this.get("/movies", (schema, request) => {
return ["Interstellar", "Inception", "Dunkirk"]
})
this.get("/movies/:id", (schema, request) => {
let id = request.params.id
return schema.movies.findBy({id});
})
},
})
Local development
- BE independence
- you can develop without internet connection
- same fixtures used for both testing and development
Example test
import { Server, Model } from "miragejs"
let server
beforeEach(() => {
server = new Server({
models: {
movie: Model,
},
routes() {
this.namespace = "api"
this.resource("movie")
},
})
})
afterEach(() => {
server.shutdown()
})
it("shows the movies", function () {
server.createList("movie", 10)
cy.visit("/")
cy.get("li.movie").should("have.length", 10)
})
Special cases
Error state
- override endpoint in a single test
it("shows an error if the save attempt fails", function () {
server.post("/questions", () => {
return new Response(500, {}, { errors: ["The database went on vacation"] })
})
cy.visit("/")
.contains("New")
.click()
.get("input")
.type("New question")
.contains("Save")
.click()
cy.get("h2").should("contain", "The database went on vacation")
})
Passthrough
new Server({
routes() {
this.get("/movies")
// All other API requests will still pass through
this.passthrough()
},
})
Alternatives
- json-server / express / nock
- MSW
- lowdb
- cy.route()
Disadvantages
- you basically build another BE
- complicated logic
- hard to decide how much your server will follow the real
- hard to estimate tasks
- doesn't support web sockets
That's it 🙂
Mirage
By Martin Nuc
Mirage
- 289