Testing Serializers in Ember

Last reviewed on November 4, 2015

Every time you generate a serializer in Ember, it generates a corresponding unit test file.

ember g serializer cat
# create app/serializers/cat.js
# create tests/unit/serializers/cat-test.js

The template for the serializer unit test looks something like this:

import { moduleForModel, test } from 'ember-qunit';

moduleForModel('cat', 'Unit | Serializer | cat', {
  // Specify the other units that are required for this test.
  needs: ['serializer:cat'],
});

// Replace this with your real tests.
test('it serializes records', function (assert) {
  var record = this.subject();

  var serializedRecord = record.serialize();

  assert.ok(serializedRecord);
});

But how do you go about testing methods like normalizeResponse, keyForRelationship, or any of the other serializer methods? In the generated code above, neither record nor serializedRecord are instances of the serializer. Even if they were, I typically don’t interact with serializers directly. I use serializers indirectly through the data store. Because of that, I have found it most useful to write my unit tests for serializers by using the store.

Testing normalizeResponse()

I have an API endpoint /cats that returns the following JSON:

[
  { "id": 1, "name": "Tubby" },
  { "id": 2, "name": "Spot" },
  { "id": 3, "name": "Chestnut" }
]

Because I am using the RESTSerializer, I want to make sure the serializer transforms this JSON payload into the proper format that it expects. If you are using the RESTSerializer, your JSON payload is expected to be in the format:

{
  "cats": [
    { "id": 1, "name": "Tubby" },
    { "id": 2, "name": "Spot" },
    { "id": 3, "name": "Chestnut" }
  ]
}

So here is my following unit test:

import { moduleForModel, test } from 'ember-qunit';
import Pretender from 'pretender';

var server;

moduleForModel('cat', 'Unit | Serializer | cat', {
  needs: ['serializer:cat'],
  beforeEach() {
    server = new Pretender(function () {
      this.get('/cats', function () {
        var response = [
          { id: 1, name: 'Tubby' },
          { id: 2, name: 'Spot' },
          { id: 3, name: 'Chestnut' },
        ];

        return [
          200,
          { 'Content-Type': 'application/json' },
          JSON.stringify(response),
        ];
      });
    });
  },
  afterEach() {
    server.shutdown();
  },
});

test('it serializes array responses', function (assert) {
  return this.store()
    .findAll('cat')
    .then(cats => {
      assert.equal(cats.get('length'), 3);
    });
});

I am using Pretender to fake out requests made to /cats and specified a static response in the same format that I expect the backend to return. Next, in my test I can access an instance of the data store (DS.Store) by calling this.store(). In order for this to be an asynchronous test, you return a promise from the test. This is noted on the ember-qunit documentation:

If you return a promise from a test callback it becomes an asyncTest. This is a key difference between ember-qunit and standard QUnit.

Inside the success callback, I write my assertions. Here I am simply checking that the length of the RecordArray is 3 which corresponds to the number of cats returned from the API.

Here is the implementation of the serializer:

// serializers/cat.js

import DS from 'ember-data';

export default DS.RESTSerializer.extend({
  normalizeResponse(store, primaryModelClass, payload, id, requestType) {
    payload = {
      cats: payload,
    };

    return this._super(store, primaryModelClass, payload, id, requestType);
  },
});

Testing keyForRelationship()

Testing keyForRelationship is similar. Every cat belongs to a home. In this example the relationship between cat and home is not asynchronous, but that doesn’t matter. Here are the models:

// models/cat.js
export default DS.Model.extend({
  name: DS.attr('string'),
  home: DS.belongsTo('home', { async: false }),
});

// models/home.js
export default DS.Model.extend({
  address: DS.attr('string'),
});

This time, each cat returned from /cats has a foreign key home_id that points to the home the cat belongs to. By default, the RESTSerializer does not relate models using the XXX_id convention. Instead, it uses the relationship key name as the default. So if I wanted my JSON to fit what Ember Data expects, it would need to look like this:

[
  { "id": 1, "name": "Tubby", "home": 1 },
  { "id": 2, "name": "Spot", "home": 1 },
  { "id": 3, "name": "Chestnut", "home": 1 }
]

Here is the test for the JSON using the XXX_id convention for related models:

import { moduleForModel, test } from 'ember-qunit';
import Pretender from 'pretender';
import Ember from 'ember';

var server;

moduleForModel('cat', 'Unit | Serializer | cat', {
  needs: ['serializer:cat', 'model:home'],
  beforeEach() {
    server = new Pretender(function () {
      this.get('/cats', function () {
        var response = [
          { id: 1, name: 'Tubby', home_id: 1 },
          { id: 2, name: 'Spot', home_id: 1 },
          { id: 3, name: 'Chestnut', home_id: 1 },
        ];

        return [
          200,
          { 'Content-Type': 'application/json' },
          JSON.stringify(response),
        ];
      });
    });
  },
  afterEach() {
    server.shutdown();
  },
});

test('belongsTo relationship uses foreign keys in the format XXX_id', function (assert) {
  Ember.run(() => {
    this.store().push({
      data: {
        id: '1',
        type: 'home',
        attributes: {
          address: '123 Ocean Boulevard, Miami, FL',
        },
      },
    });
  });

  return this.store()
    .findAll('cat')
    .then(cats => {
      assert.equal(
        cats.objectAt(0).get('home.address'),
        '123 Ocean Boulevard, Miami, FL'
      );
    });
});

In the needs array, I need to specify the related home model. Because my relationship is synchronous, I need to put the corresponding home into the store. Now when I retrieve all cats, I take the first one from the result and check if the related home model is correct by simply checking the address property.

Here is the implementation of keyForRelationship:

// serializers/cat.js

import DS from 'ember-data';

export default DS.RESTSerializer.extend({
  normalizeResponse(store, primaryModelClass, payload, id, requestType) {
    payload = {
      'deploy-requests': payload,
    };

    return this._super(store, primaryModelClass, payload, id, requestType);
  },

  keyForRelationship: function (key, relationship) {
    if (relationship === 'belongsTo') {
      return key.underscore() + '_id';
    }
  },
});

Conclusion

Here is the full source code for the examples used in this post and all the passing tests.

Personally I have found testing serializers through the store to be the most straightforward since I never directly interact with serializers. If you test serializers differently, let me know your approach in the comments!


Pro Ember Data cover
Pro Ember Data available now!

Are you struggling with Ember Data?

In my new book Pro Ember Data, you will learn how to work with Ember Data efficiently, from APIs, adapters, and serializers to polymorphic relationships, using your existing JavaScript and Ember knowledge. This book will teach you how to adapt Ember Data to fit your custom API.

What You'll Learn

  • Review the differences between normalization and serialization
  • Understand how the built-in adapters and serializers in Ember Data
  • Understand how the built-in adapters and serializers in Ember Data work
  • Customize adapters and serializers to consume any API and write them from scratch
  • Handle API errors in Ember Data
  • Work with the Reddit API using Ember Data
  • Learn how to use polymorphic relationships
Check out Pro Ember Data on Amazon using my affiliate link
You can also find Pro Ember Data on Apress.

I've been enjoying @iamdtang's Pro Ember Data book. It's to the point and has lots of great examples. My favorite part was about error handling. This book is perfect after you've exhausted the official guides.

Ilya Radchenko

Product Developer at Applied Geographics

Ilya Radchenko

Pro Ember Data is such an approachable book @iamdtang! Good job. Loving the book so far! Thank you!

Lenora Porter

Software Engineer at Heroku

Lenora Porter

Great to see all those topics in which I struggled when I started working on Ember. Nice job, will check it out :)

AbulAsar S

Fullstack Developer

AbulAsar S

I haven't read David's new book, but his previous Ember Data book helped me a lot when I was starting out with Ember, so I am sure this book is a must if you're working with Ember Data!

Kenneth Larsen

Ember Learning Core Team member

Kenneth Larsen

I bought Ember Data in the Wild back in 2016 and found it really helpful. Looking forward to reading this.

@EmberLinks

Ember related news by Chris Masters

@EmberLinks