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
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

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