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