4

I am working with MSW and OpenAPI-backend package. I want to mock the booth browser server and test server. I have OpenAPI definition available form with I generate generated.ts for RTK Query (out of scope for this question). I want to use OpenAPI spec to use it with OpenAPI Backend and generate MSW rest worker for browser and for test.

Setup is next:

index.tsx

import worker from './mocks/browser';

if (process.env.NODE_ENV === 'development') {
    worker.start();
}

mock/browser.ts

import { setupWorker, rest } from 'msw';

import { OpenAPIBackend } from 'openapi-backend';
import type { Document } from 'openapi-backend';
import definition from './api.json';


// create our mock backend with openapi-backend
const api = new OpenAPIBackend({ definition: definition as Document });
api.register('notFound', (c, res, ctx) => res(ctx.status(404)));
api.registerHandler('notImplemented', async (c, req, res) => {
    const { status, mock } = await api.mockResponseForOperation(
        c.operation.operationId as string
    );
    return res.status(status).json(mock);
});
api.register('validationFail', (c, res, ctx) =>
    res(ctx.status(400), ctx.json({ error: c.validation.errors }))
);

const worker = setupWorker(
    rest.get('/*', (req) =>
        api.handleRequest({
            ...req,
            path: req.url.pathname,
            headers: req.headers.all(),
            method: req.method,
            body: req.body,
        })
    )
);


export default worker;

api.JSON

{
  "openapi": "3.0.1",
  "info": {
    "title": "Fetch API",
    "description": "Source of truth for Fetch dashboard",
    "version": "0.1.5"
  },
  "paths": {
    "/config": {
      "get": {
        "tags": [
          "Configuration"
        ],
        "summary": "Retreive configuration object",
        "description": "Returns configuration object (map) containing configuration parameters for UI (Map<String, String>)",
        "responses": {
          "200": {
            "description": "successfull operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  },
                  "description": "Map serialized to json object.",
                  "example": {
                    "FA_COLOR": "red",
                    "FA_NAME": "fetch"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/links": {
      "get": {
        "tags": [
          "Notifications & Links",
          "Walking Skeleton"
        ],
        "summary": "List all defined links for hospital",
        "description": "Retreives all defined links for hospital. Hospital ID is indirectly obtained from user identity.",
        "responses": {
          "200": {
            "description": "successfull operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Link"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/kpis": {
      "get": {
        "tags": [
          "KPIS"
        ],
        "summary": "List all KPIs for hospital(s) that current user is managing.",
        "description": "Retreives all KPIs available for hospitals that current user is managing.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/KPI"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "KPI": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "revenue",
              "labour"
            ]
          },
          "hospital_id": {
            "type": "string",
            "description": "id of hospital that this KPI describes"
          },
          "goal": {
            "type": "number",
            "description": "full month goal"
          },
          "actual": {
            "type": "number",
            "description": "actual result"
          },
          "mtd_goal": {
            "type": "number",
            "description": "month to date goal, so that we can track projected fulfillment of goal."
          },
          "details": {
            "type": "object",
            "description": "Semi-structured way of describing details of calculation. Every KPI will potentialiy be described differently."
          }
        },
        "required": [
          "id",
          "hospital_id",
          "goal",
          "actual",
          "mtd_goal"
        ]
      },
      "Link": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "unique id of link"
          },
          "hospital_id": {
            "type": "string",
            "description": "id of hospital that this Link is configured for"
          },
          "title": {
            "type": "string",
            "description": "human readable title for URL"
          },
          "description": {
            "type": "string",
            "description": "Description of current link (alt text , or tooltip)"
          },
          "url": {
            "type": "string",
            "description": "Location of external resource"
          },
          "urgent": {
            "type": "boolean",
            "description": "Is urgency of notification elevated?"
          },
          "count": {
            "type": "number",
            "description": "Actual notification value. How many 'tasks' are waiting manager in external system."
          },
          "updated_at": {
            "type": "integer",
            "description": "Date/time of last notification update",
            "format": "int64"
          },
          "children": {
            "type": "array",
            "description": "Since notifications are possibly presented in hierarchy all children of this notification will be gathered here",
            "items": {
              "$ref": "#/components/schemas/Link"
            }
          }
        },
        "required": [
          "id",
          "hospital_id",
          "title",
          "url"
        ],
        "example": [
          {
            "id": 1,
            "hospital_id": "001",
            "title": "Link1",
            "description": "description of Link1",
            "url": "https://www.example.com/link1",
            "urgent": true,
            "count": 1,
            "updated_at": 1631113184221,
            "children": [
              {
                "id": 2,
                "hospital_id": "001",
                "title": "Link2",
                "description": "description of Link2",
                "url": "https://www.example.com/link2",
                "urgent": true,
                "count": 1,
                "updated_at": 1631113184221
              }
            ]
          },
          {
            "id": 3,
            "hospital_id": "002",
            "title": "Link3",
            "description": "description of Link3",
            "url": "https://www.example.com/link3",
            "urgent": false,
            "count": 2,
            "updated_at": 1631113184221
          }
        ]
      }
    }
  }
}

component.tsx

const { data: links, error, isLoading } = useGetLinksQuery({});

Which is fethcing localhost:3000/links

Error I am getting is:

mockServiceWorker.js:222 [MSW] Uncaught exception in the request handler for "GET http://localhost:3000/links":

Error: Unknown operation
    at OpenAPIValidator.validateRequest (http://localhost:3000/static/js/vendors~main.chunk.js:63911:13)
    at OpenAPIBackend.<anonymous> (http://localhost:3000/static/js/vendors~main.chunk.js:54246:45)
    at async OpenAPIBackend.handleRequest (http://localhost:3000/static/js/vendors~main.chunk.js:54152:22)

This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses
getResponse @ mockServiceWorker.js:222
async function (async)
getResponse @ mockServiceWorker.js:175
handleRequest @ mockServiceWorker.js:113
async function (async)
handleRequest @ mockServiceWorker.js:112
(anonymous) @ mockServiceWorker.js:271

Network tabs give:

Request URL: http://localhost:3000/links
Request Method: GET
Status Code: 500  (from service worker)
Referrer Policy: strict-origin-when-cross-origin

All related to article: https://dev.to/epilot/testing-react-with-jest-and-openapi-mocks-8gc and https://testing-library.com/docs/react-testing-library/example-intro/

Thank you.

Bojan Golubovic
  • 141
  • 2
  • 11
  • 1
    I found it, the issue was in the actual OpenAPI definition file, api.JSON. Every endpoint definition was missing operationId as param. After adding it, it worked. Did not get into more details of the actual problem, will update if I have time. Thank you. – Bojan Golubovic Sep 15 '21 at 09:46

1 Answers1

2

In your OpenApi u don't have any operationId. You need to setup a unique Id by route.

{
  "openapi": "3.0.1",
  "info": {
    "title": "Fetch API",
    "description": "Source of truth for Fetch dashboard",
    "version": "0.1.5"
  },
  "paths": {
    "/config": {
      "get": {
        "tags": [
          "Configuration"
        ],
     "operationId": "config",
        "summary": "Retreive configuration object",
        "description": "Returns configuration object (map) containing configuration parameters for UI (Map<String, String>)",
        "responses": {
          "200": {
            "description": "successfull operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  },
                  "description": "Map serialized to json object.",
                  "example": {
                    "FA_COLOR": "red",
                    "FA_NAME": "fetch"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/links": {
      "get": {
        "tags": [
          "Notifications & Links",
          "Walking Skeleton"
        ],
     "operationId": "links",

        "summary": "List all defined links for hospital",
        "description": "Retreives all defined links for hospital. Hospital ID is indirectly obtained from user identity.",
        "responses": {
          "200": {
            "description": "successfull operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Link"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/kpis": {
      "get": {
        "tags": [
          "KPIS"
        ],
        "summary": "List all KPIs for hospital(s) that current user is managing.",
        "description": "Retreives all KPIs available for hospitals that current user is managing.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/KPI"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "KPI": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "revenue",
              "labour"
            ]
          },
          "hospital_id": {
            "type": "string",
            "description": "id of hospital that this KPI describes"
          },
          "goal": {
            "type": "number",
            "description": "full month goal"
          },
          "actual": {
            "type": "number",
            "description": "actual result"
          },
          "mtd_goal": {
            "type": "number",
            "description": "month to date goal, so that we can track projected fulfillment of goal."
          },
          "details": {
            "type": "object",
            "description": "Semi-structured way of describing details of calculation. Every KPI will potentialiy be described differently."
          }
        },
        "required": [
          "id",
          "hospital_id",
          "goal",
          "actual",
          "mtd_goal"
        ]
      },
      "Link": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "unique id of link"
          },
          "hospital_id": {
            "type": "string",
            "description": "id of hospital that this Link is configured for"
          },
          "title": {
            "type": "string",
            "description": "human readable title for URL"
          },
          "description": {
            "type": "string",
            "description": "Description of current link (alt text , or tooltip)"
          },
          "url": {
            "type": "string",
            "description": "Location of external resource"
          },
          "urgent": {
            "type": "boolean",
            "description": "Is urgency of notification elevated?"
          },
          "count": {
            "type": "number",
            "description": "Actual notification value. How many 'tasks' are waiting manager in external system."
          },
          "updated_at": {
            "type": "integer",
            "description": "Date/time of last notification update",
            "format": "int64"
          },
          "children": {
            "type": "array",
            "description": "Since notifications are possibly presented in hierarchy all children of this notification will be gathered here",
            "items": {
              "$ref": "#/components/schemas/Link"
            }
          }
        },
        "required": [
          "id",
          "hospital_id",
          "title",
          "url"
        ],
        "example": [
          {
            "id": 1,
            "hospital_id": "001",
            "title": "Link1",
            "description": "description of Link1",
            "url": "https://www.example.com/link1",
            "urgent": true,
            "count": 1,
            "updated_at": 1631113184221,
            "children": [
              {
                "id": 2,
                "hospital_id": "001",
                "title": "Link2",
                "description": "description of Link2",
                "url": "https://www.example.com/link2",
                "urgent": true,
                "count": 1,
                "updated_at": 1631113184221
              }
            ]
          },
          {
            "id": 3,
            "hospital_id": "002",
            "title": "Link3",
            "description": "description of Link3",
            "url": "https://www.example.com/link3",
            "urgent": false,
            "count": 2,
            "updated_at": 1631113184221
          }
        ]
      }
    }
  }
}