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.
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.
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 association
s:
// 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.
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.
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.
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'
});
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});
}