Моки без лишней мороки с mswjs+faker.js
Привет, Хабр! Меня зовут Виктор, я фронтенд-работчик в Admitad. Моя команда делает личный кабинет клиентов. Недавно я в очередной раз столкнулся с типичной проблемой: для создания нового функционала фронтенд и бэкенд нужно было реализовывать параллельно. Но как делать фронт, не имея 100% рабочих эндпойнтов на бэкенде? Сегодня я расскажу о том, какие подходы применял, и разберу их плюсы и минусы.
О проблеме
Как я уже сказал выше, проблема заключалась в том, что мне было необходимо отобразить полученные в результате запроса на сервер данные в одном из разделов приложения. Но на бэкенде этот эндпойнт был еще не готов. Стандартный выход из подобной ситуации это использование mock-данных.
Mock-объект — тип объектов, реализующих заданные аспекты моделируемого программного окружения (c)Википедия
Как организовать получение mock-данных?
Для решения проблемы есть несколько вариантов. Начнем с самого популярного.
Пишем сервер на node.js
Мы всегда можем написать свой mock-server на node.js используя такие фреймворки, как express или koa.
Особых сложностей в этом нет, особенно, если вы уже знакомы с node.js. Например, на express код простейшего сервера будет выглядеть таким образом:
import express from 'express'; const app = express(); const port = 3000; app.get('/', (req, res) => < res.send('This is Main!'); >); app.listen(port, () => < console.log(`App listening at http://localhost:$`); >);
О более продвинутом можно почитать тут.
К плюсам данного подхода я отношу серьезные возможности в настройке сервера и огромное количество туториалов по его использованию. Основные минусы, на мой взгляд, в том, что необходимо запускать такой сервер отдельно от разрабатываемого приложения.
Используем внешние API сервисы
Уверен, многие знакомы с таким сервисом готовых API, как jsonplaceholder.typicode.com, в котором есть как готовые энднпойнты, например, /posts, /comments. так и возможность добавлять свои, причем, либо создав в репозитории своего проекта файл db.json с фейковыми данными, либо с помощью приложения Mockend. Главный, на мой взгляд, минус заключается в том, что такой подход не позволяет использовать например POST-запросы.
Используем Mock Service Worker + Faker
Этот способ мне показался наиболее удобным, поскольку позволяет вести разработку в пределах основного проекта, легко подключая необходимые эндпойнты и отключая уже ненужные. В отличие от предыдущего способа, он предоставляет такие возможности как обработка всех REST-запросов и генерация набора данных с помощью моделей.
Итак, наши инструменты:
- Mock Service Worker(далее MSW) — перехватывает запросы на сетевом уровне в браузере, обрабатывает их и возвращает ответы;
- Faker — генерирует данные в соответствии с нашими потребностями;
Работает всё следующим образом: настраиваем service worker, который будет обрабатывать наши сетевые запросы и возвращать сгенерированные нами фэйковые данные, пока бэкенд в разработке. Таким образом работа двух команд может вестись параллельно. А когда бэкенд будет готов, достаточно будет сделать сборку без флага, запускающего mock-сервис.
Две основные концепции с которыми работает MSW, это:
- request handler — обработчик запроса, который определяет, должен ли быть замокан запрос
- response resolver — функция, принимающая перехваченный запрос и возвращающая замоканный ответ
Обработчики запросов
MSW содержит в себе два набора методов для создания обработчиков для работы с API: rest и graphql.
- rest.get()
- rest.post()
- rest.put()
- rest.patch()
- rest.delete()
- rest.options()
Первым аргументом методы принимают URL запроса, а вторым response resolver.
Пример из документации:
import < setupWorker, rest >from 'msw' const worker = setupWorker( rest.get('/users/:userId', (req, res, ctx) => < const < userId >= req.params return res( ctx.json(< id: userId, firstName: 'John', lastName: 'Maverick', >), ) >), ) worker.start()
Пример
Я создал простой проект с использованием mswjs + faker в качестве mock-сервера. Код проекта находится в репозитории. Фронт написан на React+TypeScript, redux в качестве стейт менеджера в связке с redux-saga, для стилей Sass. Файловая структура такая:
Пройдемся по папкам:
- в api хранится реализация отправки сетевых запросов
- в components понятно, компоненты
- в mocks все, что связано с mock-сервером
- в modules вынесен функционал связанный с каждой отдельной сущностью, в моем случае это Students
- в store находится все, что отвечает за состояние приложения
Для создания проекта я использовал create-react-app:
npx create-react-app studentList
Далее установил faker и Mock Service Worker как devDependencies:
Настройки MSW
Для начала инициализируем Mock Service Worker используя следующую команду:
npx msw init public/ --save
В результате в папке /public будет создан файл mockServiceWorker.js со всеми необходимыми настройками для подключения сервис-воркера для mock-сервера.
После чего создадим файл src/mocks/browser.js, в котором подключим обработчики запросов:
// src/mocks/browser.js import < setupWorker >from 'msw'; import < handlers >from './handlers'; export const worker = setupWorker(. handlers);
Внимание! Использовать Mock Service Worker в production сборке крайне не рекомендуется!
Используем переменную окружения NODE_ENV, для проверки окружения, для которого собирается приложение в src/index.js:
// src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; if (process.env.NODE_ENV === 'development') < const < worker >= require('./mocks/browser'); worker.start(); > ReactDOM.render(, document.getElementById('root'));
После выполнения команды npm start и запуска приложения откроем браузер и увидим в консоли Developer Tools сообщение о том, что mock-server запущен.

Значит все правильно настроили.
Подводные камни
Описанный пример взят из документации, но с ним все не так просто. Если приложение сразу после загрузки отправляет сетевые запросы (а именно так и работает наше приложение), вначале посыпятся 404-е ответы. Так происходит, потому что регистрация сервис-воркера — процесс асинхронный. Поэтому старт приложения и необходимые для этого запросы необходимо выполнить после окончания этого процесса:
Добавляем обработчики запросов
Список студентов я получаю по запросу GET /students . Создадим для него обработчик:
// /src/mocks/handlers/students.ts import from 'msw'; import < STUDENTS_LIST_ROUTE_MASK >from '../mocks.constants'; import from '../db'; import < IStudent >from '../../components/StudentCard/StudentCard.types'; export const getStudentsHandler = rest.get(STUDENTS_LIST_ROUTE_MASK, ( req, res, ctx)=> < // Генерируем мо модели массив из шести студентов // и возвращаем его в ответе let students:IStudent[] = []; for (let i = 0; i < 6; i += 1) < students.push(db.student.create()); >return res( ctx.json(< students >) ) >)
Модель данных
Для генерации списка студентов используем библиотеку для моделирования данных @mswjs/data и faker.
// /src/mocks/models/StudentModel import < primaryKey >from '@mswjs/data'; import faker from 'faker'; export const StudentModel = < id: primaryKey(() =>faker.random.number(10000).toString()), firstName: ()=> faker.name.firstName(), lastName: () => faker.name.lastName(), age: () => faker.datatype.number(), email: () => faker.internet.email(), phone: () => faker.phone.phoneNumber(('+7 (###) ###-##-##')), city: () => faker.address.cityName(), company: () => faker.company.companyName(), avatar: () => faker.image.avatar(), information: () => faker.lorem.words(10), >;
В каждой модели прописываем данные, которые faker будет генерировать на каждый запрос. Апишка фейкера выглядит немного необычно, но довольно удобно, это очень дружелюбный DSL, реализованный с помощью цепочек выполнения, разделенный по категориям, которых более 20 (!). Используем следующие категории:
Image
Содержит в себе методы для генерации изображений. Например, для того чтобы сгенерировать URL, содержащий аватар 128×128 пикселей нужно вызывать
Таким образом мы получим URL, например:
Company
Возвращает информацию связанную с компанией, это может быть: companySuffix, catchPhrase, или множество других полей, нам понадобится метод companyName.
Internet
Тут нам понадобятся методы email, url. Названия говорят сами за себя.
Datatype
Генерирует случайную последовательность таких типов, как:
- number — целые числа, предел которых можно ограничить полями min и max
- float — действительные числа
- arrayElements — массив
- и т.д.
В файле db.ts для подключения созданной модели используем функцию factory из @mswjs/data:
// /src/mocks/db.ts import < factory >from '@mswjs/data'; import from './models' export const db = factory(< student: StudentModel >)
Запустив приложение, в браузере увидим сгенерированный список студентов:

По каждому запросу в консоли можно увидеть подробную информацию от MSW:

Для того, чтобы сервис-воркер перестал перехватывать тот или иной запрос, можно просто отключить/удалить соответствущий обработчик.
Вывод
Описанный подход существенно упрощает и ускоряет разработку, поскольку делает фронтенд-команду менее зависимой от бэкенда. Основными преимуществами в таком походе для меня стали:
- возможность запуска mock-сервера непосредственно из основного проекта
- легкое переключение между реальными и фейковыми эндпойнтами
- быстрая и гибкая настройка обработчиков запросов
Делитесь в комментариях мнениями о таком варианте mock-сервера.
Usage
Using Faker is as easy as importing it from @faker-js/faker .
import faker > from '@faker-js/faker'; // or, if desiring a different locale // import < fakerDE as faker >from '@faker-js/faker'; const randomName = faker.person.fullName(); // Rowan Nikolaus const randomEmail = faker.internet.email(); // Kassandra.Haley@erich.biz
const faker > = require('@faker-js/faker'); // or, if desiring a different locale // const < fakerDE: faker >= require('@faker-js/faker'); const randomName = faker.person.fullName(); // Rowan Nikolaus const randomEmail = faker.internet.email(); // Kassandra.Haley@erich.biz
For more information about changing and customizing the locales, please refer to our Localization Guide.
Browser
script type="module"> import faker > from 'https://cdn.skypack.dev/@faker-js/faker'; // Caitlyn Kerluke const randomName = faker.person.fullName(); // Rusty@arne.info const randomEmail = faker.internet.email(); script>
Using the browser is great for experimenting . However, due to all of the strings Faker uses to generate fake data, Faker is a large package. It’s > 5 MiB minified. Please avoid deploying the full Faker in your web app.
CDN/Deno
import faker > from 'https://cdn.skypack.dev/@faker-js/faker'; const randomName = faker.person.fullName(); // Willie Bahringer const randomEmail = faker.internet.email(); // Tomasa_Ferry14@hotmail.com
It is highly recommended to use version tags when importing libraries in Deno, e.g: import < faker >from «https://cdn.skypack.dev/@faker-js/faker@v7.4.0» . Add ?dts to import with type definitions: import < faker >from «https://cdn.skypack.dev/@faker-js/faker@v7.4.0?dts» .
Alternative CDN links
esm:
- https://esm.sh/@faker-js/faker
- https://cdn.jsdelivr.net/npm/@faker-js/faker/+esm
cjs:
TypeScript Support
Faker supports TypeScript out of the box, so you don’t have to install any extra packages.
In order to have Faker working properly, you need to check if these compilerOptions are set correctly in your tsconfig file:
"compilerOptions": "esModuleInterop": true, "moduleResolution": "Node" > >
Reproducible results
Normally Faker will give you different random values each time it is used.
faker.music.genre(); // "Soul" faker.music.genre(); // "Reggae"
If you want consistent results, you can set your own seed:
faker.seed(123); const firstRandom = faker.number.int(); // Setting the seed again resets the sequence. faker.seed(123); const secondRandom = faker.number.int(); console.log(firstRandom === secondRandom);
When upgrading to a new version of Faker, you may get different values for the same seed, as the underlying data (lists of names, words etc) may have changed.
There are a few methods which use relative dates for which setting a random seed is not sufficient to have reproducible results, for example: faker.date.past , faker.date.future , faker.date.birthdate , faker.date.recent , faker.date.soon and faker.git.commitEntry . This is because these methods default to creating a date before or after «today», and «today» depends on when the code is run. To fix this, you can specify a fixed reference date as a Date or string, for example:
// creates a date soon after 2023-01-01 faker.date.soon( refDate: '2023-01-01T00:00:00.000Z' >);
or alternatively you can set a default reference date for all these methods:
// affects all future faker.date.* calls faker.setDefaultRefDate('2023-01-01T00:00:00.000Z');
Simple data generation
Faker provides a simpleFaker that can be used to generate data that are not based on any locales like numbers and strings.
Also helpers like arrayElement or multiple are available.
This is useful if you just want to generate e.g. uuid s for your test environment, but don’t want/need to initiate/load a full Faker instance, which would include at least 500KB of locale data.
import simpleFaker > from '@faker-js/faker'; const uuid = simpleFaker.string.uuid();
See more about SimpleFaker in the API docs.
Create complex objects
Faker mostly generates values for primitives. This is because in the real world, most object schemas simply look very different. So, if you want to create an object, you most likely need to write a factory function for it.
For our example, we use TypeScript to strongly type our model. The models we will use are described below:
import type SexType > from '@faker-js/faker'; type SubscriptionTier = 'free' | 'basic' | 'business'; interface User _id: string; avatar: string; birthday: Date; email: string; firstName: string; lastName: string; sex: SexType; subscriptionTier: SubscriptionTier; >
As you can see, your User model probably looks completely different from the one you have in your codebase. One thing to keep an eye on is the subscriptionTier property, as it is not simply a string, but only one of the strings defined in the SubscriptionTier type ( ‘free’ or ‘basic’ or ‘business’ ). Also, in a real scenario, your model should not depend on a type of a third party library ( SexType in this case).
Let’s create our first user factory function:
import faker > from '@faker-js/faker'; interface User . > function createRandomUser(): User return _id: faker.string.uuid(), avatar: faker.image.avatar(), birthday: faker.date.birthdate(), email: faker.internet.email(), firstName: faker.person.firstName(), lastName: faker.person.lastName(), sex: faker.person.sexType(), subscriptionTier: faker.helpers.arrayElement(['free', 'basic', 'business']), >; > const user = createRandomUser();
At this point, we have a perfectly working function that will work for most purposes. But we can take this a step further. Currently, all properties are just randomly generated. This can lead to some undesirable values being produced. For example: The sex property having value ‘female’ while firstName is ‘Bob’ .
Let’s refactor our current code:
import faker > from '@faker-js/faker'; function createRandomUser(): User const sex = faker.person.sexType(); const firstName = faker.person.firstName(sex); const lastName = faker.person.lastName(); const email = faker.internet.email( firstName, lastName >); return _id: faker.string.uuid(), avatar: faker.image.avatar(), birthday: faker.date.birthdate(), email, firstName, lastName, sex, subscriptionTier: faker.helpers.arrayElement(['free', 'basic', 'business']), >; > const user = createRandomUser();
As you can see, we changed the order in which we generate our values. First, we generate a sex value to use it as input for the generation of firstName . Then we generate the lastName . Here, we could also pass in the sex value as argument, but in our use-case there are no special cases in where a female last name would differ from a male one. By doing this first, we are able to pass both names into the email generation function. This allows the value to be more reasonable based on the provided arguments.
But we can take this even another step further. Opposite to the _id property that uses an uuid implementation, which is unique by design, the email property potentially isn’t. But, in most use-cases, this would be desirable.
Faker has your back, with another helper method:
import faker > from '@faker-js/faker'; function createRandomUser(): User const sex = faker.person.sexType(); const firstName = faker.person.firstName(sex); const lastName = faker.person.lastName(); const email = faker.helpers.unique(faker.internet.email, [ firstName, lastName, ]); return _id: faker.string.uuid(), avatar: faker.image.avatar(), birthday: faker.date.birthdate(), email, firstName, lastName, sex, subscriptionTier: faker.helpers.arrayElement(['free', 'basic', 'business']), >; > const user = createRandomUser();
By wrapping Faker’s email function with the unique helper function, we ensure that the return value of email is always unique.
The faker.helpers.unique is targeted to be removed from Faker in the future.
Please have a look at the issue #1785.
We will update these docs once a replacement is available.
Congratulations, you should now be able to create any complex object you desire. Happy faking .
Инструкция, как расположить несколько классов на TypeScript в песочнице Plunker — TypeScript — Ответ 13944059
В данной инструкции покажу, как подключить стороннюю библиотеку. Например, подключим библиотеку glMatrix, которая используется для работы с линейной алгеброй (матрицы, вектора и операции с ними). Продолжим писать пример выше. Добавим свойство «Identity» в класс «MyClass», которое будет возвращать единичную матрицу, а метод «Main» из класса «Program» будет выводить единичную матрицу на экран.
- Откройте предыдущий пример: https://next.plnkr.co/edit/d3c. yW?preview
- Откройте файл «RequireConfig.ts» и добавьте подключение библиотеки «gl-matrix»:
1 2 3 4 5 6 7 8
requirejs.config({ baseUrl: ".", paths: { "gl-matrix": "https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min" } }); requirejs(["Program"], (Program) => { });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
import { mat4 } from "gl-matrix"; export default class MyClass { private readonly _identity: mat4 = null; public constructor() { // Creates a new identity mat4 this._identity = mat4.create(); } public get Identity(): string { return this._identity; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import MyClass from "./MyClass"; class Program { public static Main(): void { let myClass = new MyClass(); let outputElement = document.getElementById("output"); outputElement.innerHTML = myClass.Identity; } } Program.Main();
| Меню пользователя @ 8Observer8 |
| Читать блог |
Борьба с капитализмом: почему создатель популярной JS-библиотеки удалил весь ее код и заменил описание словом Liberty. Кейс Faker.js

Две популярные JavaScript-библиотеки с открытым исходным кодом — faker.js и colors.js — после обновления перестали работать: при попытке вызвать код из первой возникает ошибка, а вторая запускает бесконечный цикл с выводом в консоль слова «LIBERTY». Судя по твиттеру, разработчик Марак Сквайрс испортил библиотеки сознательно — и это не первый случай, когда популярное ПО с открытым исходным кодом неожиданно перестает работать по вине его создателей. Разбираемся, что делать, если подобная ситуация произошла с вашим приложением.
Что произошло
Faker.js — опенсорсный проект, который позволяет генерировать случайные данные для тестирования. Colors.js добавляет в консоль Node.js цвета и форматирование, чем помогает читаемости текста. По данным GitHub, где был опубликован исходных код обеих библиотек, faker.js за последнюю неделю скачали 2,4 млн раз, colors.js — 23 млн раз. По статистике GitHub, эти библиотеки используются в 19 тыс. проектов, среди которых, например, Amazon Cloud Development Kit.
4 января исходный код faker.js был удален с GitHub, а в описании библиотеки осталась одна строчка: «Что на самом деле произошло с Аароном Шварцем ?». Спустя несколько дней перестала работать colors.js — теперь пакет заставляет любое приложение с зависимостью от него бесконечно отрисовывать последовательность случайных символов со словом «LIBERTY», записанным в три строчки.

В ответ на жалобы пользователей Марак Сквайрс писал, что знает о ситуации и обещал исправить проблемы. 7 января GitHub заблокировал разработчику доступ к его репозиториям.
Сквайерс никак не прокомментировал причины своего поступка. В 2020 году в своем твиттере он писал, что «больше не готов бесплатно поддерживать компании из списка Fortune 500 и потребовал либо выдать ему «контракт с шестизначной зарплатой, либо форкнуть проект и заниматься им самостоятельно». После удаления faker.js Сквайерс написал в твиттере «Что на самом деле произошло с Аароном Шварцем?» и приложил ссылку на Reddit, где осуждалась теория заговора о Шварце: якобы он был убит после того, как обнаружил детское порно на серверах Массачусетского технологического института.
Что-то подобное уже случалось?
В 2016 году произошла похожая ситуация — разработчик Азер Кочулу из-за конфликта с мессенджером Kik (это же название носил один из его пакетов) отозвал все 250 созданных им библиотек с открытым исходным кодом для Node.js из пакетного менеджера NPM . В результате менеджер фактически перестал работать — без созданного Кочулу left-pad он не смог установить зависимости между пакетами. Тогда руководство NPM отменило отзыв пакета и запретило удалять библиотеки старше 24 часов.
Читайте также: В Java нашли уязвимость, угрожающую сотням тысяч серверов. Разбираемся, в чем суть проблемы и как ее решить
Другая история произошла с аналогом faker.js для PHP, Faker. В 2019 году разработчики перестали поддерживать библиотеку, но сообщество сделало форк и продолжает её развивать.
Как защититься от таких ситуаций?
Существует несколько подходов:
- Бэкапы зависимостей. Например, инфраструктурые инженеры (девопсеры) используют подход, когда репозиторий вместе с кодом для изменения конфигурации содержит зависимости модулей и библиотек. Это помогает защититься в ситуации, если модуль будет внезапно удален. Но у такого подхода есть минус — пакеты реже получают обновления.
- Форк библиотеки. Как в ситуации с Faker, когда исходный код из основного репозитория был удален, а версия сообщества осталась и существует до сих пор.
- Дублирование библиотек. Хранение библиотек в корпоративной сети, например, в закрытом репозитории.
Как выбрать опенсорс-библиотеку
Основной риск при выборе пакета с открытым исходным кодом — высокая вероятность того, что в какой-то момент он перестанет поддерживаться и развиваться. Разработчики опенсорс-проектов крайне редко получают деньги за свою работу. Кроме того, они могут перегореть, заболеть или умереть. В этом случае либо в библиотеку перестанут вноситься изменения, либо, как в случае с Faker, будет удален исходный код.
Поэтому при выборе библиотеки стоит проверить, как часто происходят изменения в коде, как выглядят issue и pull request, участвует ли разработчик в жизни проекта или активно ли сообщество. Стоит обращать внимание на оценки и количество скачиваний: чем популярнее проект, тем выше вероятность того, что он будет развиваться и не будет удален.
Преимущество опенсорса заключается в том, что любой человек может скопировать код и изменить его. Чем больше сообщество у проекта, тем выше вероятность, что он будет существовать долго.
Никогда не останавливайтесь: В программировании говорят, что нужно постоянно учиться даже для того, чтобы просто находиться на месте. Развивайтесь с нами — на Хекслете есть сотни курсов по разработке на разных языках и технологиях
