Jul 06, 2015
Table of contents:
Last week we went from static HTML to calling a property on the controller to retrieve data for an Ember template.
We also created a very simple model object so that we could get the title attribute and display it on the page.
However, in the real world you would never hard code your data into the controller. The next step is to move towards getting data from a JSON API.
When writing automated tests, it’s important that you create an environment that allows your tests to run in isolation. This means you don’t want to actually hit a server to retrieve data because this introduces external dependencies.
So instead of hitting an external server, we need a way to mock the responses so we can run Acceptance Tests.
This allows us to set up the many different situations and scenarios that your application will find itself in.
In today’s tutorial we are going to look at setting up a mock server and returning data to the Acceptance Test.
The first thing we need to do is to remove the hard coded data from the Controller and instead query the Ember Data store. If you remember back to Finding, Creating and Saving records with Ember Data, you have access to the Ember Data store from your Controller and Routes:
import Ember from "ember";
export default Ember.Controller.extend({
open: function () {
return this.store.find("task", { status: "open" });
}.property(),
});
This will make a GET
request to /tasks?status=open
. If you want to pass query parameters you can simply pass an object as the second parameter to the find()
method.
Now if you run the tests again everything should be broken.
Last week we created a standard Ember.Object
to represent the Task model. In order to use Ember Data we need to extend DS.Model
instead:
import DS from "ember-data";
export default DS.Model.extend({
title: DS.attr("string"),
status: DS.attr("string"),
});
To update the object simply copy and paste the above code into the task.js
file under the models
directory.
So far we only require two model properties. We will look at extending this model to include other stuff in future tutorials.
If you run the tests again they should still fail.
The reason why the tests are failing is because Ember is attempting to request data from your API, however the API does not exist yet.
In order to make the tests pass we need to mock the API. Mocking the API is important because we don’t want to actually hit a live server during the test run.
There are a number of different ways in which you can mock XHR Requests in Javascript applications. Two of the most popular open source libraries are Pretender and Fauxjax.
However mocking your API whilst testing can also lead to difficulties.
An interesting Ember addon I recently stumbled across is Ember CLI Mirage. This package is built upon Pretender for hijacking the network requests, but also allows you to create fixtures and factories that make writing tests and setting up a development environment a lot easier.
This allows you to spend less time dealing with the headache of setting up your mocks and more time concentrating on your actual Ember application.
It also makes it much easier to set up very specific situations so you can ensure your application deals with things correctly.
And finally, Ember CLI Mirage also allows you to run your application in development without requiring a development API server. This means your client development can be totally isolated from the progress (or lack thereof) of your API.
To install Ember CLI Mirage, run the following command in Terminal:
ember install ember-cli-mirage
After the installation process has run you will have a new directory called mirage
under app
.
The first thing we need to do is to add the tasks endpoint to Ember Mirage’s config.js
file.
If you open the config.js
file you will see a big page of comments. These comments are documentation for how to set up your endpoints.
By default we could just write the following to mock the /api/tasks
endpoint:
export default function () {
this.get("/api/tasks");
}
However because we need to pass the status
query parameter, we need to do something slightly more complicated:
export default function () {
this.get("/api/tasks", function (db, request) {
return { tasks: db.tasks.where(request.queryParams) };
});
}
The second parameter to the get()
method accepts a function that takes an instance of db
and request
.
The db
allows you to query the database of fixtures that Ember Mirage will create for you on the fly.
The request
is the current HTTP request.
So as you can see in the example above, I’m simply querying the db
by passing the request.queryParams
to the where()
method.
I’m then returning an object of the results that will be the JSON response from the API.
Ember Mirage is not only for your tests, it also allows you to work effectively in development without having to set up a development server to hit.
This is huge because you don’t want to slow the progress of your Client side development because you have to wait for your Server side development. Also, this makes your Client side code totally encapsulated meaning your can ship this project around without having to worry about connecting to a development server to get the data.
To return data whilst developing we need to create a Fixtures file. Create a new file called tasks.js
under the fixtures
directory in the mirage
directory:
export default [
{
id: 1,
title: "Task item 1",
status: "open",
},
{
id: 2,
title: "Task item 2",
status: "closed",
},
{
id: 3,
title: "Task item 3",
status: "open",
},
];
This is simply an array of objects that will be returned from the endpoint when making requests.
This means now that you should be able to fire up the application and see tasks 1 and 3 displayed on the page (because those are the only “open” ones).
When writing Acceptance tests, you don’t want to be using Fixtures because you are going to need to set up data for the different situations you want to test.
Instead you need a way to create responses on the fly.
Ember Mirage allows you to create Factories that can be used to set up any data you want to be returned for a particular test.
Create a new file called task.js
under the factories
directory of mirage
:
import Mirage from "ember-cli-mirage";
export default Mirage.Factory.extend({
title: function (i) {
return "Task item " + i;
},
status: function (i) {
return i % 2 === 0 ? "open" : "closed";
},
});
The Factory is simply an object that extends the Mirage.Factory
class.
For each property of the resource you can define what should be returned.
In my example above I’m using the id of the object and passing it into a function to create dynamic values.
The title
property will be Task item n
where n
is the current id.
The status
will be either open
or closed
depending on if the i
is divisible by 2.
Now that we have Ember Mirage set up we can start to use it in our Acceptance tests:
test("Count the number of tasks", function (assert) {
server.createList("task", 3);
visit("/");
andThen(function () {
var tasks = find(".task-list .task");
assert.equal(tasks.length, 2);
var task = find(".task-list .task:eq(0)");
assert.equal(task.text(), "Task item 0");
});
});
The only thing to note here is the following line:
server.createList("task", 3);
This is telling Ember Mirage to create 3 new Task
objects. Ember Mirage will automatically hijack the request to the server and return this data.
The tests we wrote earlier should now pass.
Ember CLI Mirage is a pretty amazing addition to your Ember development toolset. You can now work on your Ember application without having to worry about the progress of your API.
This is beneficial for a number of reasons. Firstly, the two different sides of the application can be developed in parallel. As long as you agree on the interface of the API, everything should just work.
Secondly, you don’t need to run a local version of the API on your computer that is going to require setting up and updating.
And finally, it can allow you to build out your Ember application as a MVP. When it comes to writing the API you will already know exactly what you need.