Acceptance testing your Ember app typically involves verifying some user behavior. For example, you may want to test that the user can view the photos on your app’s index route.
Many of these tests rely on a given server state. In other words, you want to test that the user can view ten photos, given ten photo resources exist on the server when the user boots the app. This is where factories come in.
Factories let you define the initial server state directly in your tests:
test('I can view the photos', function() {
server.createList('photo', 10);
visit('/');
andThen(function() {
assert.equal( find('img').length, 10 );
});
});
server.createList
looks up the photo
factory (the file /app/mirage/factories/photo.js
), and generates 10 database records from it, using the attributes generated by the factory. This way, Mirage’s database is populated when the Ember app boots and makes an AJAX request to fetch the photos data.
Note that Mirage’s server is restarted after each test. This means the database will start out empty at the beginning of each test.
The purpose of factories is to put code that’s highly relevant to a test as close to that test as possible. In the example above, we wanted to verify that the user would see ten photos, given those photos existed on the server. So, the server.createList('photo', 10)
call was directly in the test.
Say we wanted to test that when the user visits a details route for a photo titled “Sunset over Hyrule,” they would see that title in an <h1>
tag. One way to accomplish this would be to update the photo factory itself:
// app/mirage/factories/photo.js
import Mirage from 'ember-cli-mirage';
export default Mirage.Factory.extend({
title: 'Sunset over Hyrule'
});
The problem with this approach is that the desired change is very specific to this test. Suppose another test wanted to verify photos with different titles were displayed. Changing the factory to suit that case would break this test.
For this reason, create
and createList
allow you to override specific attributes that your factory has defined. This lets us keep relevant code near our tests, without making them brittle.
To override attributes, simply pass in an object as the last argument to create
or createList
with the attributes you want to override. Here’s what this may look like for the photos example.
First, let’s make our factory more generic:
// app/mirage/factories/photo.js
import Mirage from 'ember-cli-mirage';
export default Mirage.Factory.extend({
title(i) { return `Photo ${i}`; } // Photo 1, Photo 2 etc.
});
Now, we can write our tests, overriding the factory-generated attributes where appropriate:
test("I can view the photos", function() {
server.createList('photo', 10);
visit('/');
andThen(function() {
assert.equal( find('img').length, 10 );
});
});
test("I see the photo's title on a detail route", function() {
var photo = server.create('photo', { title: 'Sunset over Hyrule' });
visit('/' + photo.id);
andThen(function() {
assert.equal( find('h1:contains(Sunset over Hyrule').length, 1 );
});
});
We override title
in the second test since it’s relevant there, but we stick with the factory-generated defaults for the first test.
You can use attribute overrides to set up related database records in your tests. Let’s say we want to write a test given that a user with 10 photos exists on the server.
We could set up the relationship like this:
test("I see the photo's title on a detail route", function() {
var user = server.create('user');
server.createList('photo', 10, { user_id: user.id });
// write your test
Now any route that requests this user and their photos will retrieve all the data.
Mirage recommends that you use factories in testing, as they make your tests more intention-revealing. If you’d like to load your fixture files during testing, use the loadFixtures
API. To have all fixtures loaded for each test, and to not use factories at all, simply delete your /mirage/factories
directory.
Typically you’ll write tests against your application’s UI, which will verify that the proper data from Mirage was returned. It also is nice to verify the data made it to the server, to give you more confidence your Ember app is making the XHR requests you expect it should be.
You can easily assert against Mirage’s database state in your tests
test('I can update the contact', function(assert) {
server.create('contact', { name: 'Lnk' });
visit('/contacts/1');
click('.edit');
fillIn('input.name', 'Link')
click('.save');
andThen(() => {
assert.equal(server.db.contacts[0].name, 'Link');
});
});
Some people prefer to assert against the actual request body, which is also possible if you overwrite the relevant route handler in your test:
test('I can update the contact', function(assert) {
assert.expect(1);
let done = assert.async();
server.put('/contacts', (db, request) => {
let params = JSON.parse(request.requestBody).contact;
assert.deepEqual(params, {...});
done();
});
server.create('contact', { name: 'Lnk' });
visit('/contacts/1');
click('.edit');
fillIn('input.name', 'Link')
click('.save');
});
Note that this route handler is only in effect for this test.
Either approach you take, you’ll have more confidence that your Ember app’s data calls are making it to your server as expected.
You should now know enough to mock out your API and test your app using Mirage!