Fixtures

Mirage also lets you use flat fixture files to seed your database with data.

In general, we recommend using factories for most situations, since they tend to keep your mock data more maintainable. But there are certainly times where fixture data makes sense.

Fixtures are nothing more than a conventional way to accomplish the following:

import { createServer } from "miragejs"

createServer({
  seeds(server) {
    server.db.loadData({
      countries: [
        { id: 1, name: "China" },
        { id: 2, name: "India" },
        { id: 3, name: "United States" },
      ],
    })
  },
})

Let's see how we can do the same thing using fixtures.

Basic usage

Fixture data can be passed into your Mirage server definition using the fixtures key:

import { createServer } from "miragejs"

createServer({
  fixtures: {
    countries: [
      { id: 1, name: "China" },
      { id: 2, name: "India" },
      { id: 3, name: "United States" },
    ],
  },
})

This data will be automatically loaded into Mirage's database as its starting data, unless you have a seeds function defined.

If you have a seeds function defined, Mirage assumes you want to use Factories to seed your data:

import { createServer } from "miragejs"

createServer({
  fixtures: {
    countries: [
      { id: 1, name: "China" },
      { id: 2, name: "India" },
      { id: 3, name: "United States" },
    ],
  },

  seeds(server) {
    // Fixtures won't be loaded automatically, because this function is defined

    server.create("post")
  },
})

But you can use Fixtures in conjunction with Factories by calling server.loadFixtures in the default scenario:

import { createServer } from "miragejs"

createServer({
  fixtures: {
    countries: [
      { id: 1, name: "China" },
      { id: 2, name: "India" },
      { id: 3, name: "United States" },
    ],
  },

  seeds(server) {
    // Load all fixture data into the development db
    server.loadFixtures()

    // Also create some db data using factories
    server.create("post")
  },
})

Typically, fixtures are extracted to separate files. We can put our country fixture data into its own fixtures/countries.js file and export it as an array:

// mirage/fixtures/countries.js
export default [
  { id: 1, name: "China", largestCity: "Shanghai" },
  { id: 2, name: "India", largestCity: "Mumbai" },
  { id: 3, name: "United States", largestCity: "New York City" },
  { id: 4, name: "Indonesia", largestCity: "Jakarta" },
  { id: 5, name: "Pakistan", largestCity: "Karachi" },
  { id: 6, name: "Brazil", largestCity: "São Paulo" },
  { id: 7, name: "Nigeria", largestCity: "Lagos" },
  { id: 8, name: "Bangladesh", largestCity: "Dhaka" },
  { id: 9, name: "Russia", largestCity: "Moscow" },
  { id: 10, name: "Mexico", largestCity: "Mexico City" },
]

Now we can import it and pass it into our server definition:

import { createServer } from "miragejs"
import countries from "./fixtures/countries"

createServer({
  fixtures: {
    countries,
  },
})

Many teams find it useful to have certain seed data stored as separate fixture files like this.

Attribute formatting

Because fixture data is read directly into Mirage's database, it's important to use camelCase for all multi-word attributes. (Mirage uses the camelCasing convention to avoid configuration for things like identifying foreign keys.)

Don't worry if your production API format doesn't use camelCase. We'll be able to customize Mirage's API format in the Serializer layer.

loadFixtures helper

As described above, if Mirage detects both fixtures and a default scenario, it won't automatically load the fixture data.

To load Fixtures into the database during development, call server.loadFixtures in the default scenario:

seeds(server) {
  server.loadFixtures()
}

server.loadFixtures() will load all fixtures. You can load fixtures selectively by passing in an argument list of fixture names to loadFixtures:

import { createServer, Model } from "miragejs"
import cities from "./fixtures/cities"
import countries from "./fixtures/countries"
import users from "./fixtures/users"

createServer({
  models: {
    country: Model,
    city: Model,
    user: Model,
  },

  fixtures: {
    countries: countries,
    cities: cities,
    users: users,
  },

  seeds(server) {
    // only load the countries and cities fixtures
    server.loadFixtures("countries", "cities")
  },
})

Just as with the default scenario, Fixtures will be ignored during tests. If you want to load fixture data in a test, you can call server.loadFixtures:

test("I can see the countries", async function (assert) {
  server.loadFixtures("countries")

  await visit("/")

  assert.dom("option.country").exists({ length: 100 })
})

Relationships

There's no special API for creating relationships using fixtures – you just need to understand how Mirage uses foreign keys to wire up relationships.

Let's say we had these models:

import { createServer, Model } from "miragejs"

createServer({
  models: {
    user: Model,

    post: Model.extend({
      author: belongsTo("user"),
    }),
  },
})

Using the ORM we can create two related models:

let chris = schema.users.create({ name: "Chris Garrett" })

schema.posts.create({
  author: chris,
  title: "Coming Soon in Ember Octane",
})

If we take a look at Mirage's database after this, we'll see this data:

// server.db.dump()

{
  "users": [{ "id": "1", "name": "Chris Garrett" }],
  "posts": [
    { "id": "1", "authorId": "1", "title": "Coming Soon in Ember Octane" }
  ]
}

As you can see, Mirage added an authorId foreign key to the post. The convention for belongsTo foreign keys is

belongsToForeignKey = `${relationshipName}Id`

In this case, a post gets an authorId, even though that relationship points to a User model. The relationship name is always used rather than the model name, because models can have multiple relationships that point to the same type of model.

Looking at the database dump above, if you wanted to recreate the same relationship graph using only Fixtures, your data would just need to match it:

import { createServer, Model } from "miragejs"

createServer({
  models: {
    user: Model,

    post: Model.extend({
      author: belongsTo("user"),
    }),
  },

  fixtures: {
    users: [{ id: "1", name: "Chris Garrett" }],
    posts: [{ id: "1", authorId: "1", title: "Coming Soon in Ember Octane" }],
  },
})

Once these fixtures are loaded into Mirage, all the ORM methods, Shorthands and Serializers would work as expected.

If this happens to be a bi-directional relationship

  models: {
    user: Model.extend({
+     posts: hasMany()
    }),

    post: Model.extend({
      author: belongsTo("user"),
    }),
  },

then Mirage will add an array of foreign keys for the new hasMany association:

// server.db.dump()

{
  "users": [{ "id": "1", "name": "Chris Garrett", "postIds": ["1"] }],
  "posts": [
    { "id": "1", "authorId": "1", "title": "Coming Soon in Ember Octane" }
  ]
}

The convention for hasMany relationship foreign keys is

hasManyForeignKey = `${singularize(relationshipName)}Ids`

All associations have their own keys, because Mirage supports arbitrary one-way relationships. If two associations are inverses of each other, as in the above case, Mirage will keep the keys on each model in sync provided you use the ORM methods.

As you can see, maintaining foreign keys and keeping them in sync across fixture files can get a little messy, which is why Mirage recommends using factories for most of your data creation.

Still, fixtures can be quite useful in certain situations, so they're a good tool to have in your toolbox.


Next, we'll wrap up this section of the guides by learning about Serializers, which let us customize how Mirage formats our data before sending it back in response to our app.