Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Mirage

Mirage is client-side server to develop, test and prototype your app. It is used for e2e tests.


Building blocks

Mirage is inspired by the ember-cli-mirage package and its approach. ember-cli-mirage docs useful to understand the internals of the BigTest Mirage and building blocks like 

Collections


Route handlersUnder the hood it uses Pretender which is a mock server library for XMLHttpRequest and Fetch APIs. In the example below the server will mock GET requests to the /api/songs endpoint and return back predefined result. Additionally there is API to mock other CRUD operations which could be found in the package documentation. Usually this stuff should be placed in /bigtest/network/config.js file.

  this.get('/api/songs', request => {
return [
200,
{'content-type': 'application/javascript'},
'[{"id": 12}, {"id": 14}]'
];
});

...

1) Return just plain object

this.get('/api/authors/current', {id: 1, name: 'Link'});


2) Specify function as the handler. The function handler you define takes two parameters, schema (your Mirage server’s ORM) and request (the Pretender request object). Consult the schema’s API for how to interact with your models (or the database directly) and Pretender’s docs for more info on the request object.

this.get('/api/events', () => {
  let events = [];

  for (var i = 0; i < 100; i++) {
    events.push({
      id: i,
      value: Math.random() * 100
    });
  };

  return events; // will be JSON.stringified by Mirage
});


Returning a Model or Collection (from schema) lets take advantage of the serializer layer.

this.get('/api/users/:id', ({ users }, request) => {
  return users.find(request.params.id);
});

...

import { Response } from '@bigtest/mirage';
this.server.put('/configuration', () => {
return new Response(422, { some: 'header' }, {
    errors: [{
title: 'RM-API credentials are invalid'
}]
});
});


DatabaseMirage server has a database which you can be interacted with in route handlers. Typically models should be used to interact with database data, but it is possible to reach into the db directly in the event for more control.

this.delete('/packages/:id', ({ packages, resources }, request) => {
let matchingPackage = packages.find(request.params.id);
let matchingResources = resources.where({
packageId: request.params.id
});

// destroy model records from db
matchingPackage.destroy();
matchingResources.destroy();

return {};
});


Here the available API methods for db can be found.

...

import { Factory, faker, trait } from '@bigtest/mirage';

export default Factory.extend({
title: () => faker.company.catchPhrase(), // here the field is a function which returns some value
  isCustom: false, // here the field is just a value
  barcode: () => Math.floor(Math.random() * 9000000000000) + 1000000000000,

location: () => {
return { name: faker.random.word() };
}
});

...

It is possible to customize the objects created by factory using the afterCreate() hook. This hook fires after the object is built (so all the attributes have been defined will be populated) but before that object is returned. It is given two arguments: the newly created object, and a reference to the server. This makes it useful if it is needed factory-created objects to be aware of the rest of the state of Mirage database, or build relationships.

afterCreate(packageObj, server) {
let provider = server.create('provider'); create here another factory and assign it as field to newly created package
packageObj.provider = provider;
packageObj.save();
}


FixturesFixtures are data files that can be used to seed mock database, either during development or within tests. In general Mirage recommends using factories over fixtures, though there are times where fixtures are suitable. To add data to a database table titles, for instance, first create the file /bigtest/network/fixtures/titles.js:

import padStart from 'lodash/padStart';

let packageTitles = new Array(50).fill().map((_, i) => {
return {
id: `pkg_title_${padStart(i, 3, '0')}`,
name: `Package Title ${i + 1}`
};
});

export default [
...packageTitles
];


loadFixtures could be called from within a test to load this data into mock database.

beforeEach(function () {
this.server.loadFixtures('titles');
// At this point this.server.db.titles would be populated with fixtures from bigtest/network/fixtures/titles.js.

...

  
return this.visit('/eholdings/providers/paged_provider', () => {
expect(ProviderShowPage.$root).to.exist;
});
});



By the time the page is displayed, the fixtures would be loaded to the database. Calling loadFixtures without arguments would instantiate all fixtures /bigtest/network/fixtures/ folder. 

...

Serializers. Serializers are responsible for formatting route handler’s response. The application serializer (/network/serializers/application.js as example in ui-eholdings) will apply to every response. To make specific customizations, per-model serializers could be defined (e.g. /network/serializers/title.js). Any Model or Collection returned from a route handler will pass through the serializer layer. Highest priority will be given to a model-specific serializer, then the application serializer, then the default serializer.

import { JSONAPISerializer, camelize } from '@bigtest/mirage';

export default JSONAPISerializer.extend({
serializeIds: 'always',

keyForModel(modelName) {
return camelize(modelName);
}
});

...

All of those exist in the BigTest Mirage and links above is a good place to get acquitted with them.


Example

A good example of BigTest and basic Mirage setup can be found at this PR to ui-checkin package. More advanced scenarios could be found ui-eholdings repo where a bunch of tests are written.

...