Работая с 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