DEV Community

Scott Becker for Olio Apps

Posted on • Originally published at olioapps.com

Faster Iteration with Mock APIs

We frequently work on front-ends along side other teams building APIs. Sometimes those APIs are not yet available. But deadlines and sprint demos are approaching. Are we to sit around twiddling our thumbs? Nope!

The approach we take is to get the API spec if available, or if not, imagine how we would want this API to look and behave.

Then we create mock API functions within our front-end application that do not make any requests, but behave exactly as if calling the real API, returning fake data.

An alternative is to use a hosted fake API server like json-server. In some cases, that might be a better choice - such as if the front-end is deployed to a staging site, and you want to be able to edit the fake responses without redeploying.

During the development phase, we prefer to use mocking functions instead, so we don't increase our dependencies or have to rely on an external service for testing.

This allows us to decouple our development process from that of other teams we are relying on, and quickly put together working demos that illustrate how the application will perform once the API is online.

Example API - a list of tips

Suppose our backend API will return a set of "tips". A tip is a short piece of content, to show to a user on start up. Think Clippy or, a "Did you know? ..." pop-up. The front end application will fetch these tips from the API and show them to the user.

We have mockups, but the API is still under development. Fortunately, the shape and contract of the API is known. If we build the UI to be data driven now, we won't have to go back and do partial rework later. When the API comes online, we can flip a switch and it should "just work".

API response shape

This API will exist on an authenticated HTTPS endpoint. Let's say its GET https://special.api/tips. According to the spec, the response JSON shape will be:

{
  "tips": [
    {
      "id": 1,
      "type": "info",
      "title": "Set a daily or weekly reminder to track your time",
      "subtitle": "Tracking",
      "description": "Over time, we can provide insights and suggestions for improvement",
      "link": "https://url/to/set-reminder",
      "label": "Add Reminder"
    },
    // ... more tips
  ]
}
Enter fullscreen mode Exit fullscreen mode

Static Typing

At Olio Apps we've been enjoying TypeScript lately, which allows us to create static types for things like this json structure:

// the tip itself
interface ApiTip {
  readonly id: number
  readonly type: string
  readonly title: string
  readonly subtitle: string
  readonly description: string
  readonly link: string
  readonly label: string
}

// the api response envelope, an object that has an array of tips
interface ApiTips {
  readonly tips: ReadonlyArray<ApiTip>
}
Enter fullscreen mode Exit fullscreen mode

Immutable Data

Note the use of readonly and ReadonlyArray<T> which enforce immutability. We use tslint-immutable to ensure all data types are immutable and checked at compile time, instead of at run time with something like Immutable.js. This yields higher performance and more straightforward code.

API Client

The following function makes a request to fetch tips:

export function getTips(): Promise<HandledResp> {
  if (useMock) {
    return fakeGetTips().then((res: Resp) => handleResp(res))
  }

  return request
    .get(buildUrl("/tips"))
    .set(authHeader())
    .type("application/json")
    .accept("application/json")
    .then((res: Resp) => handleResp(res))
    .catch((res: Resp) => handleResp(res))
  }
}
Enter fullscreen mode Exit fullscreen mode

The Mock API Function

Note the useMock check. If this configuration variable is true, we'll call fakeGetTips() instead of making a request.

Here's what fakeGetTips() looks like:

export function fakeGetTips(): Promise<Resp> {
  const payload: ApiTips = {
    tips: [
      {
        "id": 1,
        "type": "info",
        "title": "Set a daily or weekly reminder to track your time",
        "subtitle": "Tracking",
        "description": "Over time, we can provide insights and suggestions for improvement",
        "link": "https://url/to/set-reminder",
        "label": "Add Reminder"
      },
      // more fake tips
    ],
  }

  return Promise.resolve({
    text: JSON.stringify(payload, null, 4),
    status: 200,
    statusText: "",
  })
}
Enter fullscreen mode Exit fullscreen mode

Just like a standard request, fakeGetTips() return a promise that resolves to a standard response object:

interface Resp {
  readonly text: string
  readonly status: number
  readonly statusText: string
}
Enter fullscreen mode Exit fullscreen mode

Standard Response Handling

In both versions, when the response value comes back in a promise, we handle it with .then((res: Resp) => handleResp(res)).

The handleResp function converts the response object into a normalized structure, where any text from the response is parsed as json and returned as items, along with the status and statusText.

const handleResp = (resp: Resp): HandledResp => {
    // if text present, parse as json, otherwise empty array
    const items: {} = resp.text ? JSON.parse(resp.text) : []
    // return normalized response object
    return ({
        items,
        status: resp.status,
        statusText: resp.statusText,
    })
}

interface HandledResp {
    readonly items: {} // can be any javascript type
    readonly status: number
    readonly statusText: string
}
Enter fullscreen mode Exit fullscreen mode

All downstream functions can now rely on items to be the response data, converted back into a javascript object. Once the tips API comes online, we can simply change useMock to false, and it should work as long as the API data structure hasn't changed.

Latest comments (1)

Collapse
 
jessekphillips profile image
Jesse Phillips

I started mocking with Wiremock. A key driver for its use was to control the response from the test. This meant that testing could easily create error conditions. The application logic didn't contain any special testing path.