Type-safe API mocking with Mock Service Worker and TypeScript

Mock Service Worker is a seamless API mocking library for browser and Node.js. It uses Service Worker API to intercept requests on the network level, meaning no more stubbing of “fetch”, “axios”, or any other request issuing client. It provides a first…


This content originally appeared on DEV Community and was authored by Artem Zakharchenko

Mock Service Worker is a seamless API mocking library for browser and Node.js. It uses Service Worker API to intercept requests on the network level, meaning no more stubbing of "fetch", "axios", or any other request issuing client. It provides a first-class experience when mocking REST and GraphQL API, and allows you to reuse the same mocks for testing, development, and debugging.

Watch this 4 minutes tutorial on mocking a basic REST API response with Mock Service Worker to get a better understanding of how this library works and feels:

Today we're going to have a practical dive-in into adding TypeScript to your API mocking experience to bring it one step further.

Why annotate mocks?

The mocks you write are a part of your application like any other piece of logic. Having a type validation is one of the cheapest and most efficient ways to ensure your mocks satisfy the data expectations towards them.

REST API

Each REST request handler has the following type signature:

type RestHandler = <RequestBody, ResponseBody, RequestParams>(mask, resolver) => MockedResponse
Enter fullscreen mode Exit fullscreen mode

This allows us to annotate three things in our REST API handlers:

  1. Request body type.
  2. Response body type.
  3. Request parameters.

Let's take a look at the UPDATE /post/:postId request that utilizes all three said generics:

import { rest } from 'msw'

// Describe the shape of the "req.body".
interface UpdatePostRequestBody {
  title: "string"
  viewsCount: string
}

// Describe the shape of the mocked response body.
interface UpdatePostResponseBody {
  updatedAt: Date
}

// Describe the shape of the "req.params".
interface UpdatePostRequestParams {
  postId: string
}

rest.update
  <UpdatePostRequestBody, UpdatePostResponseBody, UpdatePostRequestParams>(
  '/post/:postId',
  (req, res, ctx) => {
    const { postId } = req.params
    const { title, viesCount } = req.body

    return res(
      ctx.json({
        updatedAt: Date.now()
      })
    )
  })
Enter fullscreen mode Exit fullscreen mode

The same generics apply to any rest request handler: rest.get(), rest.post(), rest.delete(), etc.

GraphQL API

A type signature for the GraphQL handlers is:

type GraphQLHandler = <Query, Variables>(args) => MockedResponse
Enter fullscreen mode Exit fullscreen mode

This means we can annotate the Query type (what gets returned in the response) and the Variables of our query.

Let's take a look at some concrete examples.

GraphQL queries

import { graphql } from 'msw'

// Describe the payload returned via "ctx.data".
interface GetUserQuery {
  user: {
    id: string
    firstName: string
    lastName: string
  }
}

// Describe the shape of the "req.variables" object.
interface GetUserQueryVariables {
  userId: string
}

graphql.query
  <GetUserQuery, GetUserQueryVariables>(
  'GetUser',
  (req, res, ctx) => {
    const { userId } = req.variables

    return res(
      ctx.data({
        user: {
          id: userId,
          firstName: 'John',
          lastName: 'Maverick'
        }
      })
    )
  })
Enter fullscreen mode Exit fullscreen mode

GraphQL mutations

Now, let's apply the same approach to a GraphQL mutation. In the case below we're having a UpdateArticle mutation that updates an article by its ID.

import { graphql } from 'msw'

interface UpdateArticleMutation {
  article: {
    title: "string"
    updatedAt: Date
  }
}

interface UpdateArticleMutationVariables {
  title: "string"
}

graphql.mutation
  <EditArticleMutation, EditArticleMutationVariables>(
  'UpdateArticle',
  (req, res, ctx) => {
    const { title } = req.variables

    return res(
      ctx.data({
        article: {
          title,
          updatedAt: Date.now()
        }
      })
    )
  })
Enter fullscreen mode Exit fullscreen mode

GraphQL operations

When it comes to capturing multiple GraphQL operations regardless of their kind/name, the graphql.operation() truly shines. Although the nature of the incoming queries becomes less predictable, you can still specify its Query and Variables types using the handler's generics.

import { graphql } from 'msw'

type Query = 
  | { user: { id: string } }
  | { article: { updateAt: Date } }
  | { checkout: { item: { price: number } } }

type Variables = 
  | { userId: string }
  | { articleId: string }
  | { cartId: string }

graphql.operation<Query, Variables>((req, res, ctx) => {
  // In this example we're calling an abstract
  // "resolveOperation" function that returns
  // the right query payload based on the request.
  return res(ctx.data(resolveOperation(req)))
})
Enter fullscreen mode Exit fullscreen mode

Bonus: Using with GraphQL Code Generator

My absolute favorite setup for mocking GraphQL API is when you add GraphQL Code Generator to the mix.

GraphQL Code Generator is a superb tool that allows you to generate type definitions from your GraphQL schema, but also from the exact queries/mutations your application makes.

Here's an example of how to integrate the types generated by GraphQL Codegen into your request handlers:

import { graphql } from 'msw'
// Import types generated from our GraphQL schema and queries.
import { GetUserQuery, GetUserQueryVariables } from './types'

// Annotate request handlers to match 
// the actual behavior of your application.
graphql.query<GetUserQuery, GetUserQueryVariables>('GetUser', (req, res, ctx) => {})
Enter fullscreen mode Exit fullscreen mode

With your data becoming the source of truth for your request handlers, you're always confident that your mocks reflect the actual behavior of your application. You also remove the need to annotate queries manually, which is a tremendous time-saver!

Advanced usage

We've covered most of the common usage examples above, so let's talk about those cases when you abstract, restructure and customize your mocking setup.

Custom response resolvers

It's not uncommon to isolate a response resolver logic into a higher-order function to prevent repetition while remaining in control over the mocked responses.

This is how you'd annotate a custom response resolver:

// src/mocks/resolvers.ts
import { ResponseResolver } from 'msw'

interface User {
  firstName: string
  lastName: string
}

export const userResolver = (user: User | User[]): ResponseResolver => {
  return (req, res, ctx) => {
    return res(ctx.json(user)
  }
})
Enter fullscreen mode Exit fullscreen mode
import { rest } from 'msw'
import { userResolver } from './resolvers'
import { commonUser, adminUser } from './fixtures'

rest.get('/user/:userId', userResolver(commonUser))
rest.get('/users', userResolver([commonUser, adminUser])
Enter fullscreen mode Exit fullscreen mode

Custom response transformers

You can create custom context utilities on top of response transformers.

Here's an example of how to create a custom response transformer that uses the json-bigint library to support BigInt in the JSON body of your mocked responses.

// src/mocks/transformers.ts
import * as JsonBigInt from 'json-bigint'
import { ResponseTransformer, context, compose } from 'msw'

// Here we're creating a custom context utility
// that can handle a BigInt values in JSON.
export const jsonBigInt =
  (body: Record<string, any>): ResponseTransformer => {
    return compose(
      context.set('Content-Type', 'application/hal+json'),
      context.body(JsonBigInt.stringify(body))
    )
  }
Enter fullscreen mode Exit fullscreen mode

Note how you can compose your custom response transformer's logic by utilizing the compose and context exported from MSW.

You can use that jsonBigInt transformer when composing mocked responses in your handlers:

import { rest } from 'msw'
import { jsonBigInt } from './transformers'

rest.get('/stats', (req, res, ctx) => {
  return res(
    // Use the custom context utility the same way
    // you'd use the default ones (i.e. "ctx.json()").
    jsonBigInt({
      username: 'john.maverick',
      balance: 1597928668063727616
    })
  )
})
Enter fullscreen mode Exit fullscreen mode

Afterword

Hope you find this article useful and learn a thing or two about improving your mocks by covering them with type definitions—either manual or generated ones.

There can be other scenarios when you may find yourself in need to cover your mocks with types. Explore what type definitions MSW exports and take a look at the library's implementation for reference.

Share this article with your colleagues and give it a shoutout on Twitter, I'd highly appreciate it! Thank you.

Useful resources


This content originally appeared on DEV Community and was authored by Artem Zakharchenko


Print Share Comment Cite Upload Translate Updates
APA

Artem Zakharchenko | Sciencx (2021-02-28T14:10:14+00:00) Type-safe API mocking with Mock Service Worker and TypeScript. Retrieved from https://www.scien.cx/2021/02/28/type-safe-api-mocking-with-mock-service-worker-and-typescript/

MLA
" » Type-safe API mocking with Mock Service Worker and TypeScript." Artem Zakharchenko | Sciencx - Sunday February 28, 2021, https://www.scien.cx/2021/02/28/type-safe-api-mocking-with-mock-service-worker-and-typescript/
HARVARD
Artem Zakharchenko | Sciencx Sunday February 28, 2021 » Type-safe API mocking with Mock Service Worker and TypeScript., viewed ,<https://www.scien.cx/2021/02/28/type-safe-api-mocking-with-mock-service-worker-and-typescript/>
VANCOUVER
Artem Zakharchenko | Sciencx - » Type-safe API mocking with Mock Service Worker and TypeScript. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/02/28/type-safe-api-mocking-with-mock-service-worker-and-typescript/
CHICAGO
" » Type-safe API mocking with Mock Service Worker and TypeScript." Artem Zakharchenko | Sciencx - Accessed . https://www.scien.cx/2021/02/28/type-safe-api-mocking-with-mock-service-worker-and-typescript/
IEEE
" » Type-safe API mocking with Mock Service Worker and TypeScript." Artem Zakharchenko | Sciencx [Online]. Available: https://www.scien.cx/2021/02/28/type-safe-api-mocking-with-mock-service-worker-and-typescript/. [Accessed: ]
rf:citation
» Type-safe API mocking with Mock Service Worker and TypeScript | Artem Zakharchenko | Sciencx | https://www.scien.cx/2021/02/28/type-safe-api-mocking-with-mock-service-worker-and-typescript/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.