При работе с приложениями с полным стеком нам часто приходится ждать, пока внутренние API-интерфейсы создадут необходимые конечные точки, что влияет на производительность и сроки проекта. Mocking API решит эту проблему, и мы сможем создавать полные интерфейсные функции, даже если API не существует.

В этом руководстве мы создадим простое React-приложение «телефонная книга», чтобы показать, как мы работаем с фиктивным API Mirage.

Что такое Мираж?

Mirage — это библиотека для имитации API, которая позволяет создавать серверные API с начальными данными.

В отличие от других фиктивных библиотек, Mirage позволяет воссоздавать динамические сценарии, которые возможны только на рабочем сервере.

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

Некоторые из лучших функций Mirage включает

  • Маршруты для обработки HTTP-запросов
  • База данных и модели для хранения данных и определения отношений
  • Фабрики и приспособления для заглушки данных, и
  • Сериализаторы для форматирования ответов HTTP

Хорошо, теперь у нас есть некоторое представление о том, что такое Mirage и о его функциях, давайте создадим простое приложение, чтобы увидеть все в действии.

Создание нашего React-приложения

Создайте новую папку phone_book и откройте ее в редакторе VSCode.

mkdir phone_book

После открытия папки phone_book теперь откройте терминал и запустите,

npx create-react-app .

Приведенная выше команда создаст приложение реакции в той же папке.

Теперь у нас есть готовая базовая настройка, давайте настроим Mirage.

Создание нашего сервера Mirage

Mirage — это сторонняя библиотека, поэтому нам придется установить ее в нашем приложении либо npm, либо yarn.

# Using npm 
npm install --save-dev miragejs 

# Using Yarn 
yarn add --dev miragejs

Мы успешно установили Mirage в наше приложение; теперь нам нужно создать сервер, который будет обрабатывать маршрутизацию наших конечных точек API.

Создайте файл server.js в папке src. В этом файле будут все коды для нашего Mock API.

# src/server.js touch src/server.js

Mirage предоставляет метод createServer для создания поддельного сервера. Он принимает кучу конфигов для создания фейкового сервера. В этом уроке мы будем использовать некоторые из них.

  • среда
  • пространство имен
  • маршруты
  • семена
  • модели

Здесь мы создаем поддельный сервер со средой development и пространством имен api. Мы также добавляем одну модель, contact,, которая будет иметь структуру данных контактов телефонной книги.

Mirage позволяет создавать серверы в разных средах, поэтому, если вы находитесь в режиме разработки, вы можете передать среду как development, чтобы загрузить на сервер некоторые начальные данные.

Вы можете передать среду как test для тестирования, чтобы она не загружала исходные данные в тестах. Вы можете создавать данные в соответствии с вашим тестовым примером.

Хорошо, у нас есть базовая настройка сервера. Следующим шагом является создание семян и маршрутов.

Создание маршрутов API и начальных данных

Мы будем использовать пакет fakerjs для создания семян. Установите его, запустив:

npm install @faker-js/faker --save-dev 
#OR 
yarn add @faker-js/faker --dev

Нашему приложению телефонной книги потребуются следующие маршруты:

  • GET /api/contacts для получения всех записей контактов
  • GET /api/contacts/:id для получения одной записи контакта
  • POST /api/contacts для создания новой записи контакта
  • PATCH /api/contacts/:id для обновления существующей записи контакта
  • УДАЛИТЕ /api/contacts/:id, чтобы удалить существующую запись контакта.

После добавления семян и маршрутов server.js будет таким.

# src/server.js

import { createServer, Model } from 'miragejs';
import { faker } from '@faker-js/faker';

const DEFAULT_CONFIG = {
  environment: 'development',
  namespace: 'api',
};

export const makeServer = ({ environment, namespace } = DEFAULT_CONFIG) => {
  return createServer({
    environment,

    namespace,

    models: {
      contact: Model,
    },

    seeds(server) {
      const LIST_LENGTH = 5;

      // loop to create a seed data
      for (let index = 1; index <= LIST_LENGTH; index++) {
        server.create('contact', {
          name: faker.name.fullName(),
          number: faker.phone.number(),
        });
      }
    },

    routes() {
      // fetch all contacts records
      this.get('/contacts', (schema) => {
        return schema.contacts.all();
      });

      // fetch a single contact record
      this.get('/contacts/:id', (schema, request) => {
        const id = request.params.id;

        return schema.contacts.find(id);
      });

      // create a new contact record
      this.post('/contacts', (schema, request) => {
        const attrs = JSON.parse(request.requestBody);

        return schema.contacts.create(attrs);
      });

      // update an existing contact record
      this.patch('/contacts/:id', (schema, request) => {
        const newAttrs = JSON.parse(request.requestBody);
        const id = request.params.id;
        const contact = schema.contacts.find(id);

        return contact.update(newAttrs);
      });

      // remove an existing contact record
      this.delete('/contacts/:id', (schema, request) => {
        const id = request.params.id;

        return schema.contacts.find(id).destroy();
      });
    },
  });
};

Поздравляем, наш фиктивный сервер готов с исходными данными и маршрутами. Давайте вызовем функцию makeServer в index.js, чтобы инициировать сервер.

Обновите index.js с помощью приведенного ниже кода.

# src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { makeServer } from './server';

if (
  process.env.NODE_ENV === 'development' &&
  typeof makeServer === 'function'
) {
  makeServer();
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Здесь мы проверяем, является ли окружение development и makeServer типом функции; только тогда мы будем вызывать функцию makeServer.

Хорошо, у нас есть сервер. В следующем разделе мы настроим внешний интерфейс и получим доступ к фиктивным API.

Повтор сеанса с открытым исходным кодом

OpenReplay – это пакет для воспроизведения сеансов с открытым исходным кодом, который позволяет вам видеть, что пользователи делают в вашем веб-приложении, помогая вам быстрее устранять неполадки. OpenReplay размещается на собственном сервере для полного контроля над вашими данными.

Начните получать удовольствие от отладки — начните использовать OpenReplay бесплатно.

Настройка внешнего интерфейса

Для внешнего интерфейса мы будем использовать пакет Chakra UI. Chakra UI — это простая, модульная и доступная библиотека компонентов, которая дает вам строительные блоки, необходимые для создания ваших приложений React.

Установите Chakra UI и его зависимости, запустив

npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion 
# OR 
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion

Запустите сервер, запустив

npm start 
# OR 
yarn start

Затем посетите http://localhost:3000/, чтобы просмотреть изменения.

Наше приложение телефонной книги будет иметь следующие функции:

  • Пользователи могут просматривать список контактов
  • Пользователи могут создать новый контакт
  • Пользователи могут редактировать контакт
  • Пользователи могут удалить контакт

Список всех контактов

Здесь в App.jsx мы извлекаем список контактов из конечной точки /api/contacts и сохраняем его в состоянии contacts.

...
const fetchContacts = useCallback(async () => {
    try {
      setIsLoading(true);
      const response = await fetch('/api/contacts');
      const contactList = await response.json();

      setContacts(contactList.contacts);
    } catch (error) {
      toast({
        title: 'Error while fetching contacts',
        description: error,
        status: 'error',
        duration: 9000,
        isClosable: true,
      });
    } finally {
      setIsLoading(false);
    }
  }, [toast]);
...

У нас также есть состояние isLoading, и мы покажем загрузчик при выборке данных; после получения данных будет отображен ContactList. Теперь запустите сервер, и вы увидите что-то вроде этого.

Отлично, наше представление списка готово. На следующем шаге мы создадим пользовательский интерфейс для добавления новых контактов.

Мы создадим компонент ContactModal, и он будет обрабатывать поток создания контактов.

ContactModal получит две пропсы isOpen и onClose, isOpen будет отвечать за отображение/скрытие модального окна, а onClose будет отвечать за закрытие модального окна, когда пользователь нажимает кнопку отмены или после создания контакта.

...
const handleCreateContact = async (e) => {
    e.preventDefault();
    const isValid = Object.values(contactErrors).every(
      (value) => value === false
    );
    if (isValid) {
      try {
        const response = await fetch('/api/contacts', {
          method: 'POST',
          body: JSON.stringify(contactData),
        });

        await response.json();
      } catch (error) {
        toast({
          title: 'Error while creating contact',
          description: error,
          status: 'error',
          duration: 9000,
          isClosable: true,
        });
      } finally {
        onClose();
      }
    } else {
      toast({
        title: 'Invalid data',
        description: 'Name or Number is invalid',
        status: 'error',
        duration: 9000,
        isClosable: true,
      });
    }
  };
...

Давайте попробуем создать контакт, нажмите кнопку Add Contact, заполните данные и нажмите кнопку «Создать», чтобы создать контакт.

Мы будем использовать тот же модал для обновления контакта, поэтому обновленный компонент ContactModal будет выглядеть так.

Здесь мы добавили еще один реквизит, selectedContact, и на его основе мы покажем обновленный вид для контакта.

handleUpdateContact будет отвечать за обновление контакта. В этом методе мы использовали опцию PATCH для обновления контакта.

...
const handleUpdateContact = async (e) => {
    e.preventDefault();
    const isValid = Object.values(contactErrors).every(
      (value) => value === false
    );
    if (isValid) {
      try {
        const response = await fetch(`/api/contacts/${selectedContact.id}`, {
          method: 'PATCH',
          body: JSON.stringify(contactData),
        });

        await response.json();
      } catch (error) {
        toast({
          title: 'Error while updating contact',
          description: error,
          status: 'error',
          duration: 9000,
          isClosable: true,
        });
      } finally {
        onClose();
      }
    } else {
      toast({
        title: 'Invalid data',
        description: 'Name or Number is invalid',
        status: 'error',
        duration: 9000,
        isClosable: true,
      });
    }
  };
...

Теперь перейдите в браузер и попробуйте поток обновления.

Чтобы построить поток удаления, нам нужно будет выбрать идентификатор контакта, чтобы взять этот идентификатор и сделать запрос на удаление, чтобы удалить контакт.

Последний шаг — передать необходимые реквизиты из компонента приложения дочерним компонентам, а также отобразить DeleteContactModal при выборе идентификатора контакта.

И это все для этой темы. Спасибо за чтение!

Краткое содержание

  • Мы обсудили, что такое Mirage, его особенности и какие проблемы решает Mirage.
  • Мы создали фиктивный сервер с маршрутами API и начальными данными.
  • Мы создали приложение phone_book и использовали Mock API.

Ресурсы

СОВЕТ ОТ РЕДАКТОРА. Чтобы узнать о другом способе имитации API для тестирования, ознакомьтесь с нашей статьей Forever Functional: Injecting For Purity.

Первоначально опубликовано на https://blog.openreplay.com.