🐺

json-serverのデータをいい感じにmappingしたい

に公開

はじめに

json-serverはサクッと使えるモックサーバーですが、シナリオが増えてくる毎にデータの管理が大変になると思います。
どうにかこうにかテストしたいシナリオ毎にデータを切り替えができるように考えてみました。

https://github.com/typicode/json-server

モチベーション

json-serverはAPIのモックとしてサクッと使えて便利ですが、E2E相当のテストを実行する際に以下のような課題がありました。

  • シナリオ毎に異なるデータセットを使いたい
  • テスト実行前後でのデータリセットが必要

これらの問題を解決するために、json-serverにデータマッピング機能を追加することにしました。

wiremockのようなツールもあると思いますが、今回はjson-serverにこだわりました。

アプローチ

json-serverの考え方を守りつつ、柔軟にデータを操作できるようにすることを心がけております。
※ファイル操作などは行わず、json-serverが提供するinterface内で完結させたい縛りを結びました。

基本的にはjson-serverを拡張する運用が現実的でserver.jsを作成し、最終的に以下のような運用を実現できるように実装しました。

e2eからデータリセットするrequestを送る
e2eからシナリオ毎のデータをinsertするrequestを送る

https://github.com/typicode/json-server/tree/v0?tab=readme-ov-file#module

実装

サーバー構成

// server.js
import { server, router } from './config.js';
import { mappingsRoutes } from './router/mappings.js';
import { commonRoutes } from './router/common.js';  // 共通のendpoint
import { scenarioHogeRoutes } from './router/system.js'; // シナリオ毎に切り出したendpoint

mappingsRoutes(server, router);
commonRoutes(server, router);
scenarioHogeRoutes(server, router);

server.use((req, res, next) => {
  console.log(`Request URL: ${req.url}, Method: ${req.method}`);
  next();
})

server.use(router)

server.listen(3001, () => {
  console.log('JSON Server is running on port 3001')
})

基本設定

// config.js
import jsonServer from 'json-server';
const middlewares = jsonServer.defaults()
export const router = jsonServer.router('db.json')
export const server = jsonServer.create()

server.use(middlewares)
server.use(jsonServer.bodyParser)

マッピング機能

// router/mappings.js
export const mappingsRoutes = (server, router) => {
  server.delete('/mappings/reset', async (req, res) => {
    try {
      console.log('(^_^)Clearing all resources via DELETE');

      const db = router.db;
      const collections = Object.keys(db.getState());

      collections.forEach(collection => {
        db.unset(collection).write();
      });

      res.status(204).end();
    } catch (error) {
      console.error('Error clearing all resources:', error);
      res.status(500).json({ error: 'Failed to clear all resources' });
    }
  }),
  server.post('/mappings/insert/*', (req, res) => {
    try {
      console.log('(^_^)Inserting mock!!!!!!!');
      console.log(`Request URL: ${req.url}`);

      const requestData = req.body;
      const db = router.db;
      Object.keys(requestData).forEach(key => {
        if (Array.isArray(requestData[key])) {
          // 既存のデータを取得し、配列としてマージ
          const existingData = db.get(key).value() || [];
          const mergedData = [...existingData, ...requestData[key]];
          db.set(key, mergedData).write();
        } else {
          // 配列でない場合は単純に上書き
          db.set(key, requestData[key]).write();
        }
      });

      console.log('New data inserted successfully');
      res.status(200).end();
    } catch (error) {
      console.error('Error inserting mock data:', error);
      res.status(500).end();
    }
  })
}

json-serverのrouterが提供するdbを利用して、データのリセットと挿入を行っています。

使用方法

データリセット

curl -X DELETE http://localhost:3001/mappings/reset

データ登録

curl -X POST http://localhost:3001/mappings/insert/users \
  -H "Content-Type: application/json" \
  -d '{
    "users": [
      {"id": 1, "name": "John", "email": "john@example.com"},
      {"id": 2, "name": "Jane", "email": "jane@example.com"}
    ]
  }'

実装時の気づき

rewritesの設定について

最初はserver.jsrewritesで階層構造のリクエストを処理しようと考えましたが、後から追加されたリソースには紐付けできないようでした。

今回は個別にルーティング設定を追加することで対応しました。

まとめ

json-serverはシュッと使えて便利なツールですが、データが増えてきたりすると運用も大変だと思います。
自前でマッピング機能を追加すると運用がかなり楽なのでおすすめです。

Discussion