Testing Serializers in Ember - Part 2

Saturday, February 6th, 2016

Not too long ago I wrote about Testing Serializers in Ember. The examples in the post went over testing the serialization process on responses and verifying methods like normalizeResponse and keyForRelationship worked correctly. But what if you want to verify that data is serialized correctly when it is sent to your API? Let me show you an approach that has worked well for me.

Let’s say I have a cat model. When I create a new cat and call save(), imagine the API expects the request payload to look like this:

{
  "cats": [
    { "name": "Frisky" }
  ]
}

as opposed to this: (we are using the RESTSerializer but this process could work for the other serializers as well)

{
  "cat": {
    "name": "Frisky"
  }
}

For this scenario, the serialize and serializeIntoHash methods can be overridden to control the outgoing data format. The actual implementation of these methods isn’t that important so you can skim over this if you’d like.

// app/serializers/cat.js
export default DS.RESTSerializer.extend({
  serialize(snapshot) {
    return {
      cats: [
        { name: snapshot.attr('name') }
      ]
    };
  },
  serializeIntoHash(data, type, record, options) {
    delete data.cat;
    Ember.merge(data, this.serialize(record, options));
  }
});

The serialize method will control the format of the data when it is sent to the server, but without overriding serializeIntoHash, the data will look like this:

{
  "cat": {
    "cats": [
      { "name": "Frisky" }
    ]
  }
}

By default, the RESTSerializer will create a root key set as the model name. I don’t want that root key in this scenario, so I am overriding serializeIntoHash to delete it.

Now let’s look at how to test that the outgoing data is formatted correctly after overriding serialize and serializeIntoHash.

The Test

Whenever I am testing serializers, I use Pretender to mock out the backend. One of the great things about Pretender is that it keeps track of requests that were handled and those that were not in 2 public properties: handledRequests and unhandledRequests.

For this situation, we can use handledRequests to access the POST request that was made to /cats and inspect the request payload to verify that the data was formatted correctly.

First we need to set up Pretender and mock our endpoint:

moduleForModel('cat', 'Unit | Serializer | cat', {
  needs: ['serializer:cat'],
  beforeEach() {
    this.server = new Pretender(function() {
      this.post('/cats', function() {
        // default RESTSerializer response format
        // so that the promise from save() resolves
        let response = {
          cat: { id: 1, name: 'Frisky' }
        };

        return [ 200, {}, JSON.stringify(response) ];
      });
    });
  },
  afterEach() {
    this.server.shutdown();
  }
});

Next, we can write our test:

test('save() sends the data formatted correctly', function(assert) {
  assert.expect(1);
  let store = this.store();

  Ember.run(() => {
    let cat = store.createRecord('cat', {
      name: 'Frisky'
    });

    cat.save().then(() => {
      let [ request ] = this.server.handledRequests;
      let requestPayload = JSON.parse(request.requestBody);
      assert.deepEqual(requestPayload, {
        cats: [
          { name: 'Frisky' }
        ]
      });
    });
  });
});

Here we are simply creating a cat record and calling save(). handledRequests is an array containing objects for each handled request. Because we are only dealing with 1 request here, we can access the first array element using array destructuring. Each handled request object has a property requestBody, which contains the request payload as a JSON string. With that, we can check to make sure that the data was sent across correctly.

Conclusion

I have to deal with a lot of APIs that don’t follow the exact conventions of the JSONSerializer, RESTSerializer, or JSONAPISerializer so I find it necessary to test every serializer customization. This process has worked great for me so far. However, if your persistence layer isn’t HTTP based, such as a local storage backend, this approach wouldn’t work since you wouldn’t be using Pretender. Have you tested a similar situation differently? If so, I would love to hear about it in the comments!

Interested in learning more about Ember Data and how to use it with any API? Check out my book Ember Data in the Wild - Getting Ember Data to Work With Your API.


Disclaimer: Any viewpoints and opinions expressed in this article are those of David Tang and do not reflect those of my employer or any of my colleagues.

comments powered by Disqus