Работая с Ember.js, я считаю ember-cli-mirage важной библиотекой для написания тестов и разработки новых функций. Mirage - идеальный инструмент для запуска вашего приложения, когда серверная часть выполняется, серверы тестовой среды не работают, или для написания содержательных интеграционных и приемочных тестов с использованием фиктивных данных 🛹. Но, как всегда, неопытным пользователям придется немного поучиться. В этой статье я объясню, как настроить и использовать ember-cli-mirage для создания фиктивных данных сервера, чтобы вы могли реализовать их в своем приложении 🎈.

В приведенном ниже коде я буду использовать простое приложение, которое мы создали в предыдущей статье о ember-data. Чтобы продолжить, не стесняйтесь клонировать этот репозиторий GitHub:

Https://github.com/thisFunction/poke-app

Перво-наперво! 🚀

Нам нужно установить библиотеку ember-cli-mirage в наше приложение ember:

ember install ember-cli-mirage

Выполнено! 💪 Перед тем, как работать с Mirage, мы хотим сделать три вещи.

Сначала в папке /tests создайте файл .eslintrc.js. В этот файл добавьте следующий код, чтобы избежать 'server' is not defined ошибки ESLint:

module.exports = {
  env: {
    embertest: true,
  },
  globals: {
    server: true,
  }
};

Во-вторых, давайте переключим наше приложение с серверной части PokéAPI на Mirage. Это делается путем добавления следующего кода в файл /config/environment.js:

if (environment === 'development') 
  ENV['ember-cli-mirage'] = {
    enabled: true,
  };
}

В-третьих, поскольку наша внутренняя часть не использует JSON API 😥, нам нужно изменить сериализатор приложения Mirage, чтобы использовать сериализатор REST. Это легко сделать, изменив содержимое файла /serializers/application.js на следующее:

import { RestSerializer } from 'ember-cli-mirage';
export default RestSerializer;

Поздравляем, теперь вы пользуетесь Mirage! К сожалению, если вы перезагрузите приложение, оно на 100% ... сломано. 😬

Пусть начинается самое интересное! 🤹🏼‍♂

Чтобы наше приложение работало, нам нужно настроить в Mirage несколько вещей. По сути, нам нужно убедиться, что Mirage перехватывает все вызовы API, которые отправляются на pokeapi.co, и вместо этого возвращает фиктивные данные.

Поскольку мы используем PokéAPI в качестве серверной части, нам необходимо добавить следующий код в файл /mirage/config.js:

export default function() {
  this.urlPrefix = 'https://pokeapi.co';
  this.namespace = 'api/v2'; 
  this.timing = 400;
}

Свойства urlPrefix и namespace используются для создания внутреннего URL-адреса и берутся непосредственно из нашего /app/adapters/pokemon.js файла. Мы будем использовать значение по умолчанию timing, оно определяет, насколько быстро или медленно Mirage будет предоставлять нам данные.

В этом файле нам также нужно определить, что происходит, когда наш сервер Mirage получает запрос на маршрут в URL-адресе и пространстве имен, которые мы определили. Например, чтобы обработать PUT запрос к https://pokeapi.co/api/v2/pokemon, нам нужно добавить this.put('pokemon') в наш файл конфигурации. Если бы у нас был запрос DELETE, нам нужно было бы определить this.delete('pokemon'). Вот удобная ссылка на все сокращенные обработчики маршрутов.

В нашем приложении мы выполняем только два запроса. Оба являются GET запросами к /pokemon route, разница в том, что один отправляется с параметром id. В нашем конфигурационном файле мы можем указать и то, и другое следующим образом:

export default function() {
  this.urlPrefix = 'https://pokeapi.co';
  this.namespace = 'api/v2';
  this.timing = 400; t
  this.get('/pokemon'); 
  this.get('/pokemon/:id');
}

Теперь, если мы обновим наше приложение, мы увидим Error: poke-app/mirage/factories/pokemon-detail must export a factories 🙀.

Давайте исследуем 🧐

Наше приложение использует Mirage в качестве серверной части… ✅

Mirage перехватывает наши GET-запросы… ✅

Mirage возвращает фиктивные данные в приложение… ❌

Мы выявили проблему, а теперь давайте определим фабрику Mirage, которая позволит нам создавать данные, которые мы хотим вернуть.

Фабрики создают фиктивные данные 🏭

Начнем с создания фабрики миражей:

ember g mirage-factory pokemon-detail

Затем, в файле /mirage/factories/pokemon-detail, мы можем проявить творческий 🧶 или скучный как захотим, если мы определим те же свойства, что и в нашей pokemon-detail модели. Вот мой подход к созданию свойств name, height, weight и sprites с помощью faker.js:

import { Factory } from 'ember-cli-mirage';
import faker from 'faker';
const randomEnding = faker.random.arrayElement(["mon","chu","saur", "lett", "ite", "lax"]);
export default Factory.extend({
  name() {
    return `${faker.random.word()}${randomPokemonNameEnding}`;
  },
  height() {
    return faker.random.number();
  },
  weight() {
    return faker.random.number();
  },
  sprites() {
    return {
      back_default: "https://via.placeholder.com/100",
      back_shiny: "https://via.placeholder.com/100",
      front_default: "https://via.placeholder.com/100",
      front_shiny: "https://via.placeholder.com/100"
    }
  }
});

Последний шаг, который нам нужно сделать, - это сообщить mirage, сколько раз мы хотим, чтобы эта фабрика собирала данные. Поскольку мы отображаем наших покемонов группами по 20 человек, давайте создадим 60 сообщений в базе данных. Таким образом, мы сможем увидеть в действии кнопки «предыдущая» и «следующая страница».

Для этого добавьте в /mirage/scenarios/default.js следующую строку:

export default function(server) {
  server.createList('pokemon-detail', 60);
}

Обновите наше приложение и… ага! Мы видим ту же ошибку консоли, только на этот раз для другого нашего маршрута 😏. Поскольку мы уже знаем, что делать, давайте приступим к делу!

ember g mirage-factory pokemon

Этот файл, /mirage/factories/pokemon, будет немного сложнее из-за того, что мы ожидаем массив из 20 имен покемонов и объектов URL на запрос. Щелкните здесь, чтобы увидеть фактический ответ API. Кроме того, нам нужно использовать метод afterCreate для обновления общего количества покемонов в каждой ранее созданной записи базы данных, потому что мы ожидаем, что это число будет таким же, когда мы делаем запрос. Взглянем:

import { Factory } from 'ember-cli-mirage';
import faker from 'faker';
const pokemonPerPage = 20
const randomEnding = faker.random.arrayElement(["mon","chu","saur", "lett", "ite", "lax"]);
export default Factory.extend({
  count(i) { 
    return (i + 1) * pokemonPerPage;
  }, 
  results(i) { 
    let pokemonCreated = 1;
    let pokemonResults = []; 
    while(pokemonCreated <= pokemonPerPage) {
      const pokemonId = (i * 10) + pokemonCreated;
      const pokemonObject = { 
        name:  `${faker.random.word()}${randomEnding}`,
        url: `https://pokeapi.co/api/v2/pokemon/${pokemonId}`
      }
      pokemonResults.push(pokemonObject);
      pokemonCreated += 1;
    }
    return pokemonResults;
  },
  
  afterCreate(post, server) {
    server.db.pokemons.update({count: post.count})
  }
});

В методе count мы создаем общее количество записей в базе данных на основе количества покемонов, которых мы хотим отображать на странице (20), и количества запусков этой фабрики.

Метод results создает массив из 20 объектов, содержащих имя и URL.

afterCreate method берет количество покемонов из этой фабричной итерации и обновляет все записи в базе данных, чтобы они имели одинаковый номер. Mirage предоставляет удобный update метод для обновления всех записей в указанной коллекции базы данных.

Это было весело! 🤸🏽‍♀️ Не забудьте обновить наш /mirage/scenarios/default.js файл и трижды создать эти данные:

export default function(server) {
  server.createList('pokemon', 3);
  server.createList('pokemon-detail', 60);
}

После перезапуска приложения все ошибки консоли исчезли! Ура! 🥳 Но если мы посмотрим на содержимое нашего приложения, оно… совершенно пустое 😞.

Какого черта !? 🤷🏽‍♂️

Давайте поместим отладчик в /mirage/scenarios/default.js

export default function(server) {
  server.createList('pokemon', 3);
  server.createList('pokemon-detail', 60);
  debugger
}

Если мы покажем console.log server.db, мы увидим, что наши фабрики создают данные, как и ожидалось: pokemonDetails: Array(60), pokemons: Array(3).

Это означает, что Mirage не отправляет данные в том же формате, который ожидает приложение. Если мы отлаживаем метод normalizeResponse в файле /app/serializers/application.js, мы видим, что полезная нагрузка представляет собой массив из трех объектов. Однако наше приложение ожидает получать эти данные по одному объекту за раз.

Финальные исправления 🔧

В файле /mirage/config.js нам нужно добавить немного логики к нашим GET запросам, чтобы Mirage отправлял наши фиктивные данные так, как этого ожидают данные ember. Поскольку PokéAPI использует разбиение на страницы, нам нужно реализовать нечто подобное. Вот мой специальный подход:

export default function() {
  this.urlPrefix = 'https://pokeapi.co';
  this.namespace = 'api/v2'; 
  this.timing = 400; 
  this.get('/pokemon', (schema, request) => {
    const offset = Number(request.queryParams.offset); 
    return schema.db.pokemons[offset/20];
  });
  this.get('/pokemon/:id', (schema, { params }) => { 
    return schema.db.pokemonDetails.find(params.id);
  }); 
}

Для первого GET запроса мы будем использовать параметр запроса offset для обслуживания правильной записи базы данных. Разделив смещение на 20 (количество покемонов на страницу), мы получим индекс нужной нам записи в базе данных. Кусок торта 🍰!

Чтобы получить правильные данные о покемонах, мы можем использовать параметр id, чтобы найти соответствующую запись в базе данных. Mirage предлагает полезный find метод, который упрощает эту задачу 🍋!

Теперь мы перезагружаем наше приложение и… мы золотые! 🏆 Наше приложение теперь полностью работает на Mirage. Это очень полезно в ряде случаев и станет основой для написания содержательных интеграционных и приемочных тестов для нашего приложения Ember.

Спасибо за внимание!
Адам Скочилас - разработчик ember.js в SoapBox