Version:

Factories are used to seed your database, either during development or within tests. Whenever you generate an object via a factory, it will automatically get added to the database, and thus get an autogenerated id.

You define factories by using the ember g mirage-factory [name] command, or creating files under the mirage/factories directory. The name of the factory is determined by the filename.

Factories have attributes, and you create objects from factory definitions using the server.create and server.createList methods.

Defining factories

Attributes can be static (strings, numbers or booleans) or dynamic (a function). Here’s a factory with some static attributes:

// mirage/factories/user.js
import { Factory } from 'ember-cli-mirage';

export default Factory.extend({
  name: 'Link',
  age: 563,
  evil: false,
});

Functions receive the sequence number i as an argument, which is useful to create dynamic attributes:

// mirage/factories/user.js
import { Factory } from 'ember-cli-mirage';

export default Factory.extend({
  name(i) {
    return 'User ' + i
  }
});

The first user generated (per test) would have a name of User 0, the second a name of User 1, and so on.

Finally, you can also reference attributes from within a dynamic attribute via this:

// mirage/factories/contact.js
import { Factory } from 'ember-cli-mirage';

export default Factory.extend({

  age: 18,

  isAdmin(i) {
    return this.age > 30;
  }

});

This even works with other dynamic attributes:

// mirage/factories/contact.js
import { Factory } from 'ember-cli-mirage';

export default Factory.extend({

  email(i) {
    return `email${i}@acme.com`;
  },

  isAdmin(i) {
    return this.email === 'email1@acme.com';
  }

});

You’ll get an error if you create an invalid cycle of dynamic attributes.

You can also customize the objects created by your factory using the afterCreate() hook. This hook fires after the object is built (so all the attributes you’ve defined will be populated) but before that object is returned.

The afterCreate() method is given two arguments: the newly created object, and a reference to the server. This makes it useful if you want your factory-created objects to be aware of the rest of the state of your Mirage database, or build relationships (as we’ll see in a moment):

// mirage/factories/contact.js
import { Factory, faker } from 'ember-cli-mirage';

export default Factory.extend({

  isAdmin: faker.random.boolean,

  afterCreate(contact, server) {
    // Only allow a max of 5 admins to be created
    if (server.schema.contacts.where({ isAdmin: true }).models.length >= 5) {
      contact.update({ isAdmin: false });
    }
  }

});

You should define the attributes of your factory as the “base case” for your objects, and override them within your tests. We’ll discuss how do to this in the Creating Objects section.

Factories and relationships

When building objects using factories, you may want to create related objects automatically. To build related objects for belongsTo relationships, you can use association helper.

Start with defining relationships in your models so that they can be introspected for the details like relationship name:

// mirage/models/article.js
import { Model, belongsTo } from 'ember-cli-mirage';

export default Model.extend({
  author: belongsTo()
});
// mirage/models/author.js
import { Model } from 'ember-cli-mirage';

export default Model.extend();

And simply indicate which attributes are associations:

// mirage/factories/article.js
import { Factory, association } from 'ember-cli-mirage';

export default Factory.extend({
  title: 'ember-cli-mirage rockzzz',
  author: association()
});

You can also pass a list of traits and overrides as arguments to association helper:

// mirage/factories/author.js
import { Factory, trait } from 'ember-cli-mirage';

export default Factory.extend({
  withNames: trait({
    firstName: 'Yehuda',
    lastName: 'Katz'
  })
});
// mirage/factories/article.js
import { Factory, association } from 'ember-cli-mirage';

export default Factory.extend({
  title: 'ember-cli-mirage rockzzz',
  author: association('withNames', { lastName: 'Dale' })
});

You can also use the afterCreate() hook (for both hasMany and belongsTo relationships):

// mirage/factories/author.js
import { Factory, faker } from 'ember-cli-mirage';

export default Factory.extend({
  firstName: faker.name.firstName,
  afterCreate(author, server) {
    server.create('post', { author });
  }
});

Because the afterCreate() hook is called with the newly created object and a reference to the server, you can construct complex object graphs to associate with your newly created object.

Traits

Factory traits make it easy to group related attributes:

// mirage/factories/post.js
import { Factory, trait } from 'ember-cli-mirage';

export default Factory.extend({
  title: 'Lorem ipsum',

  published: trait({
    isPublished: true,
    publishedAt: '2010-01-01 10:00:00'
  })
});

You can pass anything into trait that you can into the base factory.

To use a trait, pass the trait name in as string argument to server.create:

server.create('post', 'published');

You can also compose multiple traits together:

// mirage/factories/post.js
import { Factory, trait } from 'ember-cli-mirage';

export default Factory.extend({
  title: 'Lorem ipsum',

  published: trait({
    isPublished: true,
    publishedAt: '2010-01-01 10:00:00'
  }),

  official: trait({
    isOfficial: true
  })
});

// Use
let officialPost = server.create('post', 'official');
let officialPublishedPost = server.create('post', 'official', 'published');

As always, you can pass in an attrs hash as the last argument for attribute overrides:

server.create('post', 'published', { title: 'My first post' });

When combined with the afterCreate() hook, traits simplify the process of setting up related object graphs. Here we create 10 posts each having 3 associated comments:

// mirage/factories/post.js
import { Factory, trait } from 'ember-cli-mirage';

export default Factory.extend({
  title: 'Lorem ipsum',

  published: trait({
    isPublished: true,
    publishedAt: '2010-01-01 10:00:00'
  }),

  withComments: trait({
    afterCreate(post, server) {
      server.createList('comment', 3, { post });
    }
  })
});

// Use
server.createList('post', 10, 'withComments');

Traits improve your test suite by pulling unnecessary knowledge about data setup out of your tests. If you’re writing a test module to verify the behavior of a comment box on a blog post page, each test will need a post to exist as part of its setup. If a post requires a user, and that user requires a session and perhaps a subscription, you don’t want to repeat this knowledge in each of your comment tests. Instead, create a trait that has meaning within your domain - say, a published post - to simplify the data setup needed to write each of your comment tests. This leads to a more concise, expressive test suite, which will help future developers who come into your codebase better understand the expected behavior of your application.

Using Faker.js

The Faker.js library is included with Mirage, and its methods work nicely with factory definitions:

// mirage/factories/user.js
import { Factory, faker } from 'ember-cli-mirage';

export default Factory.extend({
  firstName() {
    return faker.name.firstName();
  },
  lastName() {
    return faker.name.lastName();
  },
  avatar() {
    return faker.internet.avatar();
  }
});

We’ve also added two methods on the faker namespace, list.cycle and list.random, which are useful if you have a set of data you want your factories to iterate through:

// mirage/factories/subject.js
import { Factory, faker } from 'ember-cli-mirage';

export default Factory.extend({
  name: faker.list.cycle('Economics', 'Philosophy', 'English', 'History', 'Mathematics'),
  students: faker.list.random(100, 200, 300, 400, 500)
});

cycle loops through the data in order, while random chooses a random element from the list each time an object is created.

View Faker’s docs for the full faker API.

Extending factories

You can extend factories:

// mirage/factories/human.js
import { Factory } from 'ember-cli-mirage';

export default Factory.extend({
  species: 'homo sapiens'
});

// mirage/factories/man.js
import Human from './human';

export default Human.extend({
  gender: 'male'
});

Creating objects

Once you’ve defined a factory for a model, you can generate data for that model using server.create and server.createList, either from within mirage/scenarios/default.js for development, or from within your acceptance tests.

# server.create(type [, attrs])

Generates a single model of type type, inserts it into the database (giving it an id), and returns the data that was added.

test("I can view a contact's details", function() {
  var contact = server.create('contact');

  visit('/contacts/' + contact.id);

  andThen(() => {
    equal( find('h1').text(), 'The contact is Link');
  });
});

You can override the attributes from the factory definition with a hash passed in as the second parameter. For example, if we had this factory

export default Factory.extend({
  name: 'Link'
});

we could override the name like this:

test("I can view the contacts", function() {
  server.create('contact', {name: 'Zelda'});

  visit('/');

  andThen(() => {
    equal( find('p').text(), 'Zelda' );
  });
});

# server.createList(type, amount [, attrs])

Creates amount models of type type, optionally overriding the attributes from the factory with attrs.

Returns the array of records that were added to the database.

Here’s an example from a test:

test("I can view the contacts", function() {
  server.createList('contact', 5);
  var youngContacts = server.createList('contact', 5, {age: 15});

  visit('/');

  andThen(function() {
    equal(currentRouteName(), 'index');
    equal( find('p').length, 10 );
  });
});

And one from setting up your development database:

// mirage/scenarios/default.js
export default function(server) {
  var contact = server.create('contact');
  server.createList('address', 5, {contactId: contact.id});
}