Возможно, вы были в ситуации, когда часть серверной части вашей архитектуры заменялась новой базой данных или технологией (например, Salesforce), и вам нужно было имитировать REST API, чтобы люди, работающие над клиентской частью вашего приложения есть с чем взаимодействовать при разработке интерфейса.

Вы можете сделать это с помощью nock, если вы имитируете межсерверную коммуникацию. Предположим, небольшая часть API для Службы пиццы выглядела примерно так:

GET all chef names
chefs/
GET details for a specific chef
chefs/:chefId
GET all pizzas made by a given chef
chefs/:chefId/pizzas
GET pizza recipe details
chefs/:chefId/pizzas/:pizzaId

Вы можете имитировать ответы на любые существующие HTTP-запросы, которые делает основная служба, с помощью удобной библиотеки nock.

Мой любимый способ сделать это - создать фиктивный объект со свойством под названием nock, значение которого является просто фиктивным для любого базового URI, который вы используете для своего API. Вот класс ES6, который создает объекты со свойством mock (полностью реализованный пример класса с тестами можно найти здесь):

import nock from 'nock';
class PizzaMock {
  constructor() {
    this.mock = null;
  }
}

Напишите несколько файлов JSON, которые будут использоваться в качестве фиктивных данных, которые будут возвращаться в теле ответа для каждой конечной точки. Вот один для / chefs /:

//chefs
[
  {
    "name" : "Antonio Banderas",
    "id" : "68c9db13-ea6b-4bbc-9b27-3f56a2d5c286"
  },
  {
    "name" : "Slavoj Zizek",
    "id" : "cc866238-b579-4550-9d46-f06a5844dec5"
  },
  {
    "name" : "Carlos Santana",
    "id" : "29e77cfe-db8c-4d66-a19a-4f63f752973b"
  },
  {
    "name" : "Gennady Golovkin",
    "id" : "a8ea68cc-e227-4cb2-b2cc-d4c8d5fcada6"
  }
]

А вот для пиццы:

//pizzas
[
  {
    "name" : "Meatlovers",
    "toppings" : ["Prosciutoo", "Italian Sausage", "Pepperoni", "Salami"]
  },
  {
    "name" : "Del Mar",
    "toppings" : ["Calamari", "Shrimp", "Garlic Sauce"]
  },
  {
    "name" : "The Shit-Show",
    "toppings" : ["Jagermeister", "Regret", "Black Olives"]
  }
]

Теперь давайте расширим наш класс PizzaMock, чтобы реализовать макеты, которые будут возвращать эти данные. Во-первых, добавьте еще пару операторов импорта, чтобы получить ваши фиктивные данные и объявить базовый URL-адрес службы пиццы (конечно, это можно настроить с помощью переменной окружения):

import nock from 'nock';
import mockChefData from './mockdata/mockchefdata';
import mockPizzaData from './mockdata/mockpizzadata';
const baseUrl = 'http://www.mypizzaserver.com';
class PizzaMock {
  constructor() {
    this.mock = null;
  }
}

Затем добавьте метод initMock () и экспортируйте класс:

import nock from 'nock';
import mockChefData from './mockdata/mockchefdata';
import mockPizzaData from './mockdata/mockpizzadata';
const baseUrl = 'http://www.mypizzaserver.com';
class PizzaMock {
  constructor() {
    this.mock = null;
  }
initMock() {
    this.mock = nock(baseUrl);
    this.mock.persist()
      .filteringPath(path => {
        path = path.replace(/\/chefs\/[a-zA-Z0-9\-]{36}/g, '/chefs/xxx');
        path = path.replace(/\/pizzas\/[a-zA-Z0-9\-]{36}/g, '/pizzas/xxx');
        return path;
      });
    this.mock.persist()
      .get('/chefs')
      .query({count : '1'})
      .reply(200, {
        chefs : [mockChefData[0]]
      });
    this.mock.persist()
      .get('/chefs')
      .reply(200, {
        chefs: mockChefData
      });
    this.mock.persist()
      .get('/chefs/xxx')
      .reply(200, {
        chef: mockChefData[3]
      });
    this.mock.persist()
      .get('/chefs/xxx/pizzas')
      .reply(200, {
        pizzas: mockPizzaData
      });
    this.mock.persist()
      .get('./chefs/xxx/pizzas/xxx')
      .reply(200, {
        pizza: mockPizzaData[0]
      });
  }
}
export default PizzaMock;

Давайте посмотрим на все, что происходит в этом методе initMock ().

Первое, что мы делаем, это создаем макет, используя наш базовый URL:

this.mock = nock(baseUrl);

Затем мы гомогенизируем все вызовы API, заменяя любые уникальные идентификаторы / параметры в URI на «xxx». Это делается для того, чтобы наш макет отправлял тот же тестовый ответ для любого запрос к данной конечной точке, независимо от уникального идентификатора (например, вызов / chefs / 3179b6c3–4e2a-40b5–9e67–25aa4a16f29d вернет тот же результат, что и вызов / chefs / b4f631b9–77b4–4e15–904f- 440b31f388f7).

this.mock.persist()
      .filteringPath(path => {
        path = path.replace(/\/chefs\/[a-zA-Z0-9\-]{36}/g, '/chefs/xxx');
        path = path.replace(/\/pizzas\/[a-zA-Z0-9\-]{36}/g, '/pizzas/xxx');
        return path;
      });

Об этой реализации стоит отметить несколько моментов:

  • Наш конкретный макет предполагает, что API всегда использует 36-символьные уникальные идентификаторы для каждого типа ресурса, и регулярное выражение, которое мы используем в каждом выражении path.replace, отражает это.
  • В конце функции обратного вызова вы указываете filteringPath (), убедитесь, что вы вернули управляемый путь!

Теперь, когда мы гомогенизировали наши пути, мы можем добавить имитацию для каждой конечной точки.

    this.mock.persist()
      .get('/chefs')
      .query({count : '1'})
      .reply(200, {
        chefs : [mockChefData[0]]
      });
    this.mock.persist()
      .get('/chefs')
      .reply(200, {
        chefs: mockChefData
      });
    this.mock.persist()
      .get('/chefs/xxx')
      .reply(200, {
        chef: mockChefData[3]
      });
    this.mock.persist()
      .get('/chefs/xxx/pizzas')
      .reply(200, {
        pizzas: mockPizzaData
      });
    this.mock.persist()
      .get('./chefs/xxx/pizzas/xxx')
      .reply(200, {
        pizza: mockPizzaData[0]
      });

Посмотрите, как я издевался над конечной точкой / chefs. Среди других удобных опций утилиты, nock позволяет вам настраивать ответы в зависимости от наличия или отсутствия параметров запроса.

this.mock.persist()
      .get('/chefs')
      .query({count : '1'})
      .reply(200, {
        chefs : [mockChefData[0]]
      });
    this.mock.persist()
      .get('/chefs')
      .reply(200, {
        chefs: mockChefData
      });

Прочтите раздел Документы, чтобы узнать больше.

Развлекайся!