Skip to content

Notes for myself

Marine Dunstetter edited this page Apr 4, 2022 · 3 revisions

Using both Express and Mirage

About

There are two different common ways to mock API calls when developing locally with Ember: Mirage and Express. To me ember-cli-mirage is THE way to easily develop complex fixtures and testing scenarios that can be used in both manual tests and acceptance tests. In the other hand, I rarely use Express. This note is about the reasons why we sometimes need both and how to set up Express.

Mirage limit

Mirage answers a lot of use cases regarding API calls. But it has a limit: it can intercept only ajax and fetch calls. This limit is not often raised, but we can meet it.

For instance, let's imagine a document preview page. When visiting the page, an image file is returned by the backend. Now, the issue here is the call that gets this image is not an ajax or fetch call. The preview is built by generating in the DOM an image element with a source set dynamically, and the browser natively goes to find this source, without using ajax or fetch.

<img src={{this.fileURL}} class="preview-image" alt={{this.filename}}>

So in other words: the API call to get the image is not caught by Mirage, and without any other mock solution but Mirage, there is no testing image that can be fetched and the preview cannot be tested manually.

Express as a second net

At the contrary of Mirage that mocks API calls by "acting like" a server, an Express server is an actual local server that runs locally. So the way calls are performed as no importance here, you can mock everything with Express, including the image preview.

Once again, Ember does everything for you 🐹❤️

Ember framework allows you to run an express server almost magically. All you need to do is:

ember g http-mock your-mock

And there you have your server folder with an Express server and your "your-mock" resource to configure. From this point, Express js documentation will be your main reference to achieve what you have in mind.

Each time you will run ember s, Ember will run both the frontend and the Express server, there is no additional action to use the Express server locally.

Something like this:

// server/mocks/your-mock.js

module.exports = function(app) {
  const express = require('express')
  let previewRouter = express.Router()

  previewRouter.get('/:id/preview.png', function(req, res) {
    res.sendFile('my-mock-image.png', {
      root: __dirname
    })
  })

  app.use('/api/someroute/preview', previewRouter)
}

(With my-mock-image.png in the same folder and /:id/preview.png the expected URL for preview)

Preventing test failure on exception

  /*
   * onUncaughtException override can be a workaround to manage a use case where an unhandled exception is the expected behaviour
   * - https://github.com/emberjs/ember-qunit/issues/592
   * - https://github.com/snewcomer/ember-stateful-promise/blob/f151a254f1ad7a060e474abd551e686e83723b8f/tests/integration/components/playground-test.js
   */
  let tmp
  hooks.before(() => {
    tmp = QUnit.onUncaughtException
    QUnit.onUncaughtException = () => {}
  })

  hooks.after(() => {
    QUnit.onUncaughtException = tmp
  })

Nested Resources

Let's imagine a form builder that allows User A to build a form dynamically. Then this form can be completed by User B. This note is about the relationship between Form and Fields. It explains why using EmberObject rather than DS.Model to define field templates and field data given a DS.Model FormModel.

Context

A common frontend issue when dealing with APIs is that some resources of the backend are “nested resources”.

Resources are identified by an id, and can be requested by id:

nested1

But sometimes, there are resources bound to other resources. They do have an id (or not), but when they have, this id is unique only in the scope of the parent resource:

nested2

Besides, these children resources can’t be requested directly by the API’s client. Their existence is only meaningful as a property of the parent resource:

nested3

This model leads to issues on the frontend side if the store is used to deal with children resources as DS.Model. Ember does a lot of work for us regarding resources fetching. Let's assume that a form's field is a DS.Model. When the application receives a form, it would then consider its fields as resources and put them in the local store:

nested4

This way, the frontend can request directly a field and a lot of bindings are assigned automatically when the models are added to the store (between forms, fields, conditional display between fields... for instance I don’t need to assign manually each field to the conditional display rule they are involved in, because Ember is about to do this by itself through relationships between models).

But there is a HUGE but. The ids of nested resources are not unique ids, they are unique only in the scope of their parent, and the local store is not able to deal with this:

nested5

In this example, form-1 and form-2 now share the same text-1 field, so if text-1 is modified by the user, both form-1 and form-2 will be impacted by this change.

Solution

As long as the backend doesn’t want to give up nested resources, one valid solution on the frontend side is the store must be a reflection of the received data:

nested6

To achieve this, we have to make our nested resources simple properties. It means that we have to replace our DS.Model for nested resources with plain objects or EmberObject if we want to benefit from EmberObject possibilities. In this late case, we will have to override all the default serializers to turn manually the plain objects from the payload into EmberObject of our custom classes, and binding manually what needs to be bound.