Мы, фронтендщики, можем контролировать огромное количество вещей на стороне клиента (в браузере или в самом веб-приложении). Мы можем реализовать лучший пользовательский опыт и наилучшим образом визуализировать данные… пока есть данные. Нет данных — нет приложения. В этот момент в игру вступает печально известная фраза Притворяйся, пока не сделаешь. Он предполагает, что, имитируя уверенность, компетентность и оптимистичный настрой, человек может реализовать эти качества в своей реальной жизни и добиться желаемых результатов. С точки зрения веб-технологий это говорит: давайте смоделируем реакцию серверной части и продолжим разработку функций для нашего клиентского приложения, как если бы у нас были реальные данные, чтобы, получив их, мы могли правильно с ними обращаться.

Действительно, мокирующий бэкэнд полезен в разных ситуациях:

  • Вы не хотите ждать реализации бэкенда. У вас есть определение Swagger, но сервиса пока нет.
  • Вы не хотите ждать тестовых пользователей. В некоторых случаях очень сложно найти тестового пользователя с определенной конфигурацией пограничного случая.
  • Возможно, вам даже не разрешено изменять тестовых пользователей, или изменение необратимо. Изображение разработки приложения для онбординга, которое должно быть показано пользователям только один раз (во время онбординга).
  • Возможно, вас просто не интересует бэкэнд, например, если вы работаете над учебником по интерфейсу или PoC.

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

Жестко закодированные значения

Предположим, у вас есть приложение, которое отображает некоторые данные о биткойнах (в наши дни все любят данные о биткойнах!). В вашем сервисе у вас может быть способ получить его из какого-либо API. Прямой подход состоял бы в том, чтобы закомментировать фактическую реализацию и заменить ее жестко запрограммированным наблюдаемым. Оператор RxJs «of» сделает всю работу за вас.

public fetchBitcoinData(): Observable<BitcoinMarketData[]> {
   //return this.http.get<BitcoinMarketData[]>('/v1/markets.json');
   return of([{...}]);
}

Если ваши данные объемные (например, 200 записей данных рынка биткойнов), вы можете сохранить их в отдельном файле и ссылаться на них в своем сервисе.

import fakeBitcoinData from '../assets/fake-data/fake-bitcoin-data.json';
public fetchBitcoinData(): Observable<BitcoinMarketData[]> {
   // ... Commented out actual implementation
   return of(fakeBitcoinData);
}

Важно, чтобы вы каким-то образом назвали свой импорт, а не использовали import * from ‘…’, иначе вместо этого ваши данные будут импортированы как модуль. Кроме того, вам нужно будет добавить «resolveJsonModule» и «esModuleInterop» в файл tsconfig.json в параметрах компилятора, как показано ниже:

{  "compilerOptions": 
   {  "resolveJsonModule": true, 
      "esModuleInterop": true 
   } 
}

Это решение будет работать нормально, но у него есть несколько ограничений:

  • Возможны только имитирующие GET-запросы
  • Насмешка над ответом на ошибку невозможна
  • Далеко от фактической реализации

Попробуем исправить последний аспект. К счастью, мы можем использовать встроенный в Angular HttpClient для запроса локальных данных. Нам просто нужно указать URL-адрес API для наших данных:

public fetchBitcoinData(): Observable<BitcoinMarketData[]> {
   //return this.http.get<BitcoinMarketData[]>('/v1/markets.json');
   return this.http.get<BitcoinMarketData[]>('assets/fake-data/fake-
   bitcoin-data.json');
}

HttpInterceptor

На случай, если вы используете внешнюю библиотеку и не имеете доступа к сервису, есть еще один нативный метод Angular для имитации ваших запросов — HttpInterceptor. Перехватчики — это уникальный тип Angular Service, который мы можем реализовать. Перехватчики позволяют нам перехватывать входящие или исходящие HTTP-запросы с помощью класса HttpClient. Перехватив HTTP-запрос, мы можем модифицировать или изменить значение запроса… или ответа!

Давайте предположим, что у вас есть библиотека, которая дает вам рекомендации, следует ли вам покупать или продавать свои биткойны. В FakeBackendHttpInterceptor вы можете проверить этот конкретный http-запрос и изменить ответ. Не забудьте указать FakeBackendHttpInterceptor в app.module.ts. Используйте для этого providers: [fakeBackendProvider].

@Injectable()
export class FakeBackendHttpInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): 
              Observable<HttpEvent<any>> {
   return this.handleRequests(req, next);
}

   handleRequests(req: HttpRequest<any>, next: HttpHandler): any {
      const { url, method } = req;
      if (url.endsWith('/suggestion') && method === 'GET') {
      return of(new HttpResponse({ status: 200, body: 'BUY'}))
         .pipe(delay(500));
      }
      // if there is not any matches return default request.
      return next.handle(req);
   }
}
/**
* Mock backend provider definition for app.module.ts provider.
*/
export const fakeBackendProvider = {
   provide: HTTP_INTERCEPTORS,
   useClass: FakeBackendHttpInterceptor,
   multi: true,
};

Если это решение — то, что вы искали, ознакомьтесь со следующей статьей — Использование Angular HttpInterceptor подобно поддельному бэкенду.

HttpInterceptor дает вам больше свободы в отношении имитации данных из внешних библиотек, но при этом накладывает дополнительные ограничения.

  • Вы должны реализовать дополнительную логику Angular в своем приложении.
  • И что еще более драматично — что, если вы забудете удалить fakeBackendProvider из провайдеров в app.module.ts перед отправкой своего кода?

Мы можем сделать лучше. Давайте посмотрим на мое любимое решение.

JSON-сервер

json-server — это библиотека JavaScript для имитации REST API. Вы можете установить его с помощью следующей команды.

npm install -g json-server

Чтобы запустить полноценный поддельный серверный сервис, вам нужно создать файл .js для сервера (например, server.js) и файл .json для вашей базы данных (например, db.json). Ваш сервер должен указывать на файл .json (jsonServer.router(‘apps/fake-backend/src/json-server-backend/db.json’)).

//server.js
const jsonServer = require('json-server');
const server = jsonServer.create();
const router = jsonServer.router('apps/fake-backend/src/json-server-backend/db.json');
const middlewares = jsonServer.defaults();
server.use(middlewares);
//server.get('/api/suggestion', (req, res) => res.status(500).jsonp({}));
server.use(jsonServer.rewriter({
   '/api/suggestion': '/suggestion',
   '/v1/*': '/bitcoinMarketData',
   })
);
// Use default router
server.use(router);
server.listen(3000, () => {
   console.log('Fake-Backend Server is running');
});

Ваша поддельная база данных должна предоставлять фиктивные ответы на маршрутизируемые запросы. Вы получите массив, на который ссылается bitcoinMarketData для вашего запроса к ‘/v1/markets.json’. «/api/suggestion» получит в ответ «SELL». Обратите внимание, что вы даже можете имитировать ответы об ошибках (см. закомментированный код в server.js).

//db.json
{
"suggestion": ["SELL"],
"bitcoinMarketData": [
   {
      "currency": "EUR",
      "high": null,
      "latest_trade": 1566979086,
      "weighted_price": null,
      "bid": 9173.0,
      "volume": 0,
      "ask": 9219.89,
      "low": null,
      "duration": 90181,
      "close": 9282.49,
      "avg": null,
      "symbol": "btcdeEUR - fake data from JSON Server",
      "currency_volume": 0
   },
   {...}
 ]
}

Как только мы закончим с настройкой сервера, мы должны сообщить Angular, чтобы он проксировал запросы к нашему поддельному бэкенду. Для этого необходимо создать параметризованную конфигурацию прокси-сервера (например, proxy.conf.js на корневом уровне).

//proxy.conf.js
var defaultTarget = 'http://api.bitcoincharts.com/';
   module.exports = [
      {
         context: ['/v1/**', '/api/suggestion*'],
         target: process.env['FAKE_BACKEND']
            ? 'http://localhost:3000': defaultTarget,
         secure: true,
         changeOrigin: true,
      },
];

Теперь нам нужно указать Angular использовать эту конфигурацию прокси в angular.json.

//angular.json
...
"serve": {
   "executor": "@angular-devkit/build-angular:dev-server",
   "options": {
      "browserTarget": "proxy-for-angular:build",
      "proxyConfig": "proxy.conf.js",
      "port": 4200
   },
...

Наконец, мы добавили несколько дополнительных скриптов в package.json для переключения между реальным и поддельным бэкендом.

"scripts": {
   ...
   "start": "nx serve",
   "fakebackend": "node apps/fake-backend/src/json-server-  
   backend/server.js",
   "start:fakebackend": "FAKE_BACKEND=true ng serve --ssl=true"
},

Что здесь происходит? Мы добавили «fakebackend», чтобы запустить наш поддельный бэкэнд. Теперь мы можем запустить «npm run start:fakebackend my-app», и запросы будут проксироваться на сервер json. Никаких жестко заданных значений, никакой дополнительной логики. Более того — готовые ответы GET, POST и сообщения об ошибках! Если вы запустите «npm run start my-app», запросы будут проксироваться на реальный сервер, «восстанавливая» исходную функциональность. Хотя единственным ограничением json-server является дополнительная зависимость от разработчиков, которую вы получаете, есть еще кое-что, чего нам не хватает.

На данный момент я говорил о точке зрения разработчика. А тестеры? Ни один из приведенных выше вариантов не поможет им протестировать пограничные случаи или обработку ошибок. Конечно, они могли бы выключить сервер и проверить его тогда. Однако я готов поспорить, что это не всегда возможно, особенно если серверная служба используется в масштабах всей компании. В этом случае вам может понадобиться дополнительная опция.

Печенье

Одним из возможных решений было бы написать сценарий для вашего промежуточного программного обеспечения (например, APIC), который проверяет наличие определенных файлов cookie, например. errorapi, код ошибки и тело ошибки. Если эти файлы cookie присутствуют в запросе, а URL-адрес API соответствует значению файла cookie errorapi, он не будет передан на фактический сервер. Вместо этого будет возвращено содержимое errorcode и errorbody. Таким образом тестер может манипулировать серверными ответами браузера.

Подводя итог, у вас есть несколько вариантов имитации ответов бэкенда в Angular — жестко заданные значения, запрос локальных данных через HttpClient, изменение ответов с помощью HttpInterceptor, установка json-сервера или настройка файлов cookie. Правильный подход зависит от ваших потребностей и потребностей ваших коллег-разработчиков и тестировщиков. Ну, хватит притворяться, давайте сделаем это!

Примеры кода из этой истории можно найти в следующем репозитории: https://github.com/korneevamg/fake-backend.

Дополнительные ресурсы: