Breaking news! I just launched Coparrot, a no-install and no-configuration mock server for mobile and web developers, on Product Hunt!
Please check it out and give it some love ❤️!
October 06, 2019 in articles
Say we need to make a React component that has following requirements:
Following those requirements, we make use of the new and shiny React hooks and come up with the following component
import React, { useState, useEffect } from 'react'
const App = () => {
const [isLoading, setIsLoading] = useState(false)
const [lastChecked, setLastChecked] = useState(Date.now())
const [data, setData] = useState(null)
const [error, setError] = useState(null)
useEffect(
() => {
setIsLoading(true)
fetch('SOME URL')
.then(response => response.json())
.then(result => {
setIsLoading(false)
setData(result.results)
})
.catch(error => {
setIsLoading(false)
setError(error)
})
},
[lastChecked]
)
return (
<div>
{isLoading && <p data-testid="loading">Loading ...</p>}
{error && <p data-testid="error">Error</p>}
{data && <p data-testid="data">{JSON.stringify(data, null, 2)}</p>}
{!isLoading && (
<button data-testid="check" onClick={() => setLastChecked(Date.now())}>
Check
</button>
)}
</div>
)
}
export default App
Now we need to make automated tests to ensure this component follows the requirements. We can use React testing library to help us write the test code.
Our App component uses fetch
to fetch data from network. To test this first requirement, we need to mock fetch
to return successfully. Then we check if an element with “data” test ID is rendered.
test('should render data', async () => {
// mock fetch
global.fetch = jest.fn().mockResolvedValue({
json: jest.fn().mockResolvedValue({ results: 'something' }),
})
const { getByTestId } = render(<App />)
await wait(() => expect(getByTestId('data')).toBeTruthy())
})
To test this requirement, we need to mock fetch
to return unsuccessfully by rejecting the promise. Then we check if an element with “error” test ID is rendered.
test('should render error', async () => {
// mock fetch
global.fetch = jest.fn().mockRejectedValue({
message: 'Something',
})
const { getByTestId } = render(<App />)
await wait(() => expect(getByTestId('error')).toBeTruthy())
})
To test this requirement, we need to mock fetch
in a way that it will resolve when we tell it to. So before we tell it to resolve, we check if an element with “loading” test ID is rendered. After we tell it to resolve, we check again if an element with “loading” test ID is not rendered.
test('should render loading', async () => {
var shouldResolve = false
// mock fetch
global.fetch = jest.fn().mockImplementation(() => {
return new Promise(resolve => {
const waitUntilShouldResolve = () => {
if (shouldResolve) {
resolve({
json: jest.fn().mockResolvedValue({ results: 'something' }),
})
} else {
setImmediate(waitUntilShouldResolve)
}
}
waitUntilShouldResolve()
})
})
const { getByTestId, queryByTestId } = render(<App />)
await wait(() => expect(getByTestId('loading')).toBeTruthy())
shouldResolve = true
await wait(() => expect(queryByTestId('loading')).toBeNull())
})
To test this requirement, we need to mock fetch
to first reject the promise. Then we simulate the button click to trigger the re-fetch and tell the mocked fetch
to resolve successfully. Then we check if an element with “data” test ID is rendered.
test('should reload on button click', async () => {
var shouldResolve = false
var shouldReject = true
// mock fetch
global.fetch = jest.fn().mockImplementation(() => {
return new Promise((resolve, reject) => {
const waitUntilShouldResolve = () => {
if (shouldReject) {
reject({ message: 'something' })
} else if (shouldResolve) {
resolve({
json: jest.fn().mockResolvedValue({ results: 'something' }),
})
} else {
setImmediate(waitUntilShouldResolve)
}
}
waitUntilShouldResolve()
})
})
const { getByTestId } = render(<App />)
await wait(() => expect(getByTestId('error')).toBeTruthy())
shouldResolve = true
shouldReject = false
const button = getByTestId('check')
fireEvent.click(button)
await wait(() => expect(getByTestId('data')).toBeTruthy())
})
With those tests, we can make sure that App component will always follow the requirements even if we make some changes in the future. You can checkout the complete code in this repo. Let me know what you think of this way of testing on Twitter.
Articles, drawings, and codes by Nico Prananta, a software developer (iOS and web) and digital artist (for fun!) in Zürich, Switzerland. I'm on Twitter.
For Indonesian iOS Developers! Gabung yuk tiap hari Sabtu jam 16:00 WIB di Belajar Bareng Swift Mingguan bareng temen-temen iOS developer di Swift Study Group Indonesia.