If you do not already have our events app running locally, you can clone it at the state we expect at the start of this exercise here: https://github.com/baroquon/events-app/tree/005.3.solution
In the last episode we looked at acceptance testing our Ember application. But, our acceptance tests were weak and fragile. To truly test our apps well, we need good data. That is where Ember CLI Mirage can help us. With Mirage we can intercept our app's API requests and use Pretender to generate mocks.
Let's get started. We are going to continue working on the
events-app
from the last couple of episodes. We'll need to do a little setup to get our events-app ready for Mirage. First, we need to create an application adapter. The application adapter will apply to all Ember models that do not have their own named adapter. For instance, an Event model will use the adapter at
app/adapters/application.js
unless we create an adapter at
app/adapters/event.js
. Let's create our application adapter. From the project root type:
$ ember g adapter application
In your new adapter add a namespace property with the value
api
:
import JSONAPIAdapter from 'ember-data/adapters/json-api';
export default JSONAPIAdapter.extend({
namespace: 'api'
});
Also, change the route to call out to Ember Data for its model. In
app/routes/events.js
uncomment the
return
and delete all of the fixture data.
import Ember from 'ember';
export default Ember.Route.extend({
model(){
return this.store.findAll('event');
}
});
Now, we are ready to get started with Mirage. Installing Mirage is as easy as installing any Ember addon. In your project's root directory type:
$ ember install ember-cli-mirage
You can think of Mirage as a fake API server. Just as we would set up routes on our server to respond to Ember's requests, we will set up routes in Mirage. To get started we will create Mirage versions of our Ember models. In our case we have an
Address
model and an
Event
model.
$ ember g mirage-model event && ember g mirage-model address
In the mirage models is where we will need to set up the relationships. In
mirage/models/event.js
:
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
address: belongsTo()
});
And add the inverse in the
Address
model.
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
event: belongsTo()
});
Now, let's create the routes to respond with these newly generated models. Right now, we'll just respond to find all and find record for addresses and events. In
mirage/config.js
add:
this.namespace = 'api';
this.get('/events', (schema) => {
return schema.events.all();
});
this.get('/events/:id', (schema, request) => {
return schema.events.find(request.params.id);
});
this.get('/addresses', (schema) => {
return schema.addresses.all();
});
this.get('/addresses/:id', (schema, request) => {
return schema.addresses.find(request.params.id);
});
This would be a good time to restart your Ember CLI server. Now, we need to add our factories.
$ ember g mirage-factory event && ember g mirage-factory address
In our factories we are going to use faker to help populate our database.
In
mirage/factories/event.js
we will add our factory details. This will map to our Ember model for the most part, but we will specify our relationships in our mirage model and implement them when we create actually populate our db. Make sure to add
faker
to our import from mirage.
import { Factory, faker } from 'ember-cli-mirage';
export default Factory.extend({
title() { return `${faker.hacker.ingverb()} ${faker.hacker.noun()} ${faker.hacker.noun()}`;},
description() { return faker.lorem.paragraph(); },
attendeeCount() { return Math.floor((Math.random() * 30) + 1);}
});
Now, you will do the same thing in your address factory.
import { Factory, faker } from 'ember-cli-mirage';
export default Factory.extend({
street1() { return faker.address.streetAddress(); },
street2() { return faker.address.secondaryAddress(); },
city() { return faker.address.city(); },
state() { return faker.address.stateAbbr(); },
zip() { return faker.address.zipCode(); }
});
Now we need to populate our factories. We will do this in two ways, first for our development database, then we will set it up for our tests.
For development we will load our db from
mirage/scenarios/default.js
. First, we'll create our addresses using server.createList. Then, we'll iterate over them and create an event for each address.
let addresses = server.createList('address', 10);
addresses.forEach((address) => {
server.create('event', { address });
});
Here we see how to create both lists and individual objects. Now, if we were to start our server, we would see a list of ten events each with an address and populated with faker data. However, if we were to run our tests, they would fail as we have removed our fixtures and the data creates in
mirage/scenarios/default.js
is only loaded in development, not tests. To load them in our tests just add the same code from above at the top of the test that expects the data to be present. For example, in
tests/acceptance/events-test.js
our test might now look like this:
test('visiting / and clicking events takes us to the events page.', function(assert) {
let addresses = server.createList('address', 3);
addresses.forEach((address) => {
server.create('event', { address });
});
visit('/');
andThen(function() {
click('a[href="/events"]');
andThen(function() {
assert.equal(currentURL(), '/events');
// This is the text that is in the events/index template - it should display if we have not selected an event
assert.equal($('article h3').text().trim(), "Choose an event on the left to see details");
assert.equal($('aside ul li').length, 3);
});
});
});
That's it, we now have a access to a great client based API for our Ember test and development environments thanks to Ember CLI Mirage and the awesome folks that maintain it.
References / Notes
- You can see the completed code here baroquon/events-app/compare/005.4.video.complete
- Ember CLI Mirage
- Sam Selikoff on Twitter
- Pretender.js
- Faker.js
Comments for Ember CLI Mirage