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 ] }
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> }
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)) } }
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: "", }) }
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 }
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 }
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.
Top comments (1)
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.
Wiremock as a Web Server for Testing
Jesse Phillips ・ Dec 1 '19 ・ 2 min read