DEV Community

Boluwatife Fakorede
Boluwatife Fakorede

Posted on • Edited on

Mocking APIs for frontend developers

Alt Text

Considering how loosely coupled web development is these days, leading to a separation of the frontend (mostly SPA) and backend(API driven) of our applications and often handled by different teams, one major thing to consider is the " blocked factor."

The blocked factor is how long a developer spends waiting on external API dependencies, hence preventing a feature development on the frontend or a project altogether.
Mocking is a way out of this blocked factor. They are easy to write, flexible, and stateless (hence testing scenarios repeatedly is easier) and ultimately provide a way out of external API dependencies.

Mocking allows us to simulate the backend API by specifying endpoints and the responses it gives.

The mocking framework

In this article, I would demonstrate how to use MSW (Mock Server Worker) to mock APIs for a todo react application.

Mock Service Worker is an API mocking library that uses Service Worker API to intercept actual requests. - mswjs.io

N.B: MSW is fully framework agnostic and also supports GraphQL. You should check it out!

Let's get started!

We need to install MSW.

$ npm install msw --save-dev
# or
$ yarn add msw --dev
Enter fullscreen mode Exit fullscreen mode

Next would be to set up mocks. I do separate the API actions such as create, read, e.t.c from the mock servers themselves just for convenience.

Let’s do that.

[
  {
    "id": "BJSON65503",
    "todo": "Write a new article",
    "completed": false
  },
  {
    "id": "BJSON44322",
    "todo": "Work on side project",
    "completed": true
  }
]

Enter fullscreen mode Exit fullscreen mode

A test sample of Todo Data. We could have used fakers also.

import todosData from './todoDB.json'

let todos: ITodo[] = [...todosData]

export interface ITodo {
  id: string
  todo: string
  completed: boolean
}

export interface TodoUpdate {
  todo?: string
  completed?: boolean
}

export interface TodoUpdate {
  todo?: string
  completed?: boolean
}

async function create(todo: ITodo): Promise<ITodo> {
  todos.push(todo)
  return todo
}

async function readAll(): Promise<ITodo[]> {
  return todos
}

async function read(todoId: string): Promise<ITodo | undefined> {
  return todos.find(todo => todo.id === todoId)
}

async function update(
  id: string,
  update: TodoUpdate,
): Promise<ITodo | undefined> {
  todos.forEach(todo => {
    if (todo.id === id) {
      return {...todo, update}
    }
  })
  return await read(id)
}

async function deleteTodo(todoId: string): Promise<ITodo[]> {
  return todos.filter(todo => todo.id !== todoId)
}

async function reset() {
  todos = [...todosData]
}

export {create, read, deleteTodo, reset, update, readAll}
Enter fullscreen mode Exit fullscreen mode

API actions

We can create our mock now.

If you are familiar with the express framework of node.js, the way to write the REST API Mock with MSW is similar.

import {setupWorker, rest} from 'msw'
import * as todosDB from '../data/todo'

interface TodoBody {
  todo: todosDB.ITodo
}

interface TodoId {
  todoId: string
}

interface TodoUpdate extends TodoId {
  update: {
    todo?: string
    completed?: boolean
  }
}

const apiUrl = 'https:todos'

export const worker = setupWorker(
  rest.get<TodoId>(`${apiUrl}/todo`, async (req, res, ctx) => {
    const {todoId} = req.body
    const todo = await todosDB.read(todoId)
    if (!todo) {
      return res(
        ctx.status(404),
        ctx.json({status: 404, message: 'Book not found'}),
      )
    }

    return res(ctx.json({todo}))
  }),

  rest.get(`${apiUrl}/todo/all`, async (req, res, ctx) => {
    const todos = await todosDB.readAll()
    return res(ctx.json(todos))
  }),

  rest.post<TodoBody>(`${apiUrl}/todo`, async (req, res, ctx) => {
    const {todo} = req.body
    const newTodo = await todosDB.create(todo)
    return res(ctx.json({todo: newTodo}))
  }),

  rest.put<TodoUpdate>(`${apiUrl}/todo/update`, async (req, res, ctx) => {
    const {todoId, update} = req.body
    const newTodo = await todosDB.update(todoId, update)
    return res(ctx.json({todo: newTodo}))
  }),

  rest.delete<TodoId>(`${apiUrl}/todo/delete`, async (req, res, ctx) => {
    const {todoId} = req.body
    const todos = await todosDB.deleteTodo(todoId)
    return res(ctx.json({todos: todos}))
  }),
)
Enter fullscreen mode Exit fullscreen mode

Server worker used for client-side mocking with all the rest endpoints

Above, we have defined all the REST APIs with their responses, and if you notice, our REST endpoints all point to an HTTPS server (the apiUrl prefix). This is because service workers are to be served over HTTPS and not HTTP (Always note that).
We could attach the response status, JSON, e.t.c, which is great and similar to how our APIs behave normally.

The setupWorker or the handler has not yet started; hence the Service worker API will intercept no request.

We will start the worker in a development mode because we don’t want to hit a mock in production or even staging.

Let’s do that

if (process.env.NODE_ENV === 'development') {
  const {worker} = require('./dev-server')
  console.log(worker)

  worker.start()
}

export {}

Enter fullscreen mode Exit fullscreen mode

All we need to do is import the above file into the entry point of our application.

//index.ts

import './server'
Enter fullscreen mode Exit fullscreen mode

Now, when we start our react application, we should see the following in the browser console.

Alt Text

Terrific!

If we make an API request to the “/todo/all” endpoint and look at the Network tab, we will see an actual request and the corresponding response served by the service worker API.

Alt Text

We will also get the todos from our todoDB.json as our response data.

Alt Text

This is great as we don’t have our backend ready and much more; we are not experiencing any blockage in our development process as frontend developers.

One of the major concerns regarding using mocks is maintenance, as the backend behavior might change rapidly, and we have to maintain the mocks. This is a valid point, but if we are to write tests (we will do in the second part of this article) for this APIs consumptions on the frontend, we would still need to maintain our mocks considering that our users won’t mock fetch or Axios hence our test shouldn’t as well, what if there is a way to share the handlers between the dev server and the test server hence maintaining just one handler and API actions.

We will explore the power of MSW much more in the next article.

Thank you for reading.

Top comments (3)

Collapse
 
kettanaito profile image
Artem Zakharchenko

Thank you for the great overview, Fakorede!

Collapse
 
edsonpro profile image
edson-pro

Msw is a cool tech

Collapse
 
tharwat997 profile image
Youssef Tharwat

Doesn't that just make frontend developers "full stack developers"?