The Big Mac index is a purchasing power parity index published by The Economist. Jest fetch testing is a tech task so frustrating that it almost made me quit the industry.
For the love of fuck trying to Jest test a thing that uses fetch() is the most frustrating experience of my life. Fuck this whole profession. pic.twitter.com/4Hbvuz8Zhs
— Swizec Teller (@Swizec) August 22, 2019
Screenshots don't have gifs, here:
I'm writing this so you don't have to read 8234721 different blog posts, stack overflow answers, and GitHub threads with conflicting info on how to get Jest and fetch to play together.
Library where I got it working 👉 bigmac-index-price-calculator.
Context is a project built from scratch without a CLI generator. Maybe that was part of my pain.
How to test fetch requests with Jest
A couple of ground rules:
- Our test suite must work offline. You shouldn't be blocked from coding just because you're on an airplane even if coding on an airplane is a terrible idea and you should really get some sleep instead.
- Our test suite must be reliable. You don't want to get different results running at different times. No expiring API keys, no changing responses, no network failures.
We're using Jest because it's winning the JavaScript and TypeScript testing market. fetch() because it's the standard way to make API requests.
Our ground rules mean we have to mock requests – make them return a static object that we set up. Recording and replaying requests would be cool, but as far as I can tell Ruby VCR hasn't made it to the JavaScript world yet.
There's at least 2 fetch mocking libraries I could find: fetch-mock and jest-fetch-mock. Both annoying as shit to set up. fetch-mock
was the one I got working in the end.
Jest tests run in node. Node doesn't have fetch. You have to use a library for that too. There's a fetch polyfills and ponyfills and Idontknowwhatelsefills out there. I found at least 3.
isomorphic-fetch is the library I got working in the end.
Let us begin.
Curated Fullstack Web Essays
Get a series of curated essays on Fullstack Web development. Lessons and insights from building software for production. No bullshit.
Install dependencies
Assuming you've got an empty project initialized with npm init
or yarn init
, you start by installing everything you'll need
yarn add --dev jest @types/jest ts-jest fetch-mock
Dev dependencies are those only used in development. We need Jest itself, its type definitions, its TypeScript sister, and fetch-mock.
Using ts-jest
gives you TypeScript testing superpowers so you don't have to compile your code first and test second. Test your TypeScript directly. It's nice.
yarn add isomorphic-fetch
We need isomorphic-fetch
both in development and when running live. That way your library doesn't break if used server-side in Gatsby or something.
Configure Jest
Configure Jest in a jest.config.js
file. There's a bunch of info out there on where exactly this configuration should live. This is the one that finally worked for me.
// jest.config.js
module.exports = {
roots: ["./src"],
transform: {
"^.+\\.tsx?$": "ts-jest",
},
}
We're saying code lives in a /src
directory and that typescript files should run with ts-jest
.
Mock fetch requests
Jest has built-in support for mocking entire libraries. That was a nice discovery after trying many approaches from many articles and tutorials.
You put files in a __mocks__
directory and they overwrite the library you're calling.
To use fetch-mock
instead of isomorphic-fetch
in your tests, create a file like this:
// src/__mocks__/isomorphic-fetch
const fetchMock = require("fetch-mock")
const fetch = fetchMock.sandbox()
module.exports = fetch
This means that every time you import isomorphic-fetch
in a test file, it will instead import a sandboxed fetch-mock
instance. You can add any other setup you want here. Anything you need for your mocks to work correctly.
BIG WARNING 👉 you have to use the require()
syntax. No ES6 imports because this is Node not JavaScript. That tripped me up for hours and rumors say you can get import from
to work, but I eventually gave up.
Write a Jest fetch test
We're ready to write a test. Everything should work.
The file I'm testing is called index.ts
so my test file is called index.test.ts
. Here's an example:
// src/index.test.ts
import { ParityPrice } from "./index";
import * as fetchMock from "isomorphic-fetch";
const URL =
"http://api.ipstack.com/check?access_key=<api key>";
describe("ParityPrice", () => {
beforeEach(() => {
fetchMock.reset();
});
test("returns Japan price for Japan", async () => {
fetchMock.mock(URL, {
status: 200,
body: {
ip: "1.33.213.230",
type: "ipv4",
continent_code: "AS",
continent_name: "Asia",
country_code: "JP",
country_name: "Japan",
region_code: "13",
region_name: "Tokyo",
city: "Tokyo",
zip: "100-6801",
latitude: 35.688838958740234,
longitude: 139.7628631591797,
location: {
geoname_id: 1850147,
capital: "Tokyo",
languages: [
{
code: "ja",
name: "Japanese",
native: "\u65e5\u672c\u8a9e"
}
],
country_flag: "http://assets.ipstack.com/flags/jp.svg",
country_flag_emoji: "\ud83c\uddef\ud83c\uddf5",
country_flag_emoji_unicode: "U+1F1EF U+1F1F5",
calling_code: "81",
is_eu: false
}
}
});
const parity = new ParityPrice(<api key>);
await expect(parity.price(149)).resolves.toBe(96);
});
});
We're in Jest now so we can use modern imports.
Rest mocks before each test so there's no overlap, then mock an API request with a matcher and the response we want. The matcher in my case is a URL string, the exact URL my code is going to call, and the response is a JSON object.
Response code 200
for success and body for the data.
Now when you call fetch(URL)
in the code, it won't make an API call, it's just gonna get this response object.
Makes your tests fast, offline, and reliable. ✌️
The Big Mac purchasing parity code we're testing
TDD says to write your test first and your code second, but I actually wrote the code first. Here's the part we're testing above :)
// src/index.ts
const fetch = require("isomorphic-fetch")
export class ParityPrice {
ipstack_key: string
constructor(ipstack_key: string, enable_ssl: boolean = false) {
this.ipstack_key = ipstack_key
}
private async ipstack(IP?: string) {
const param = IP ? IP : "check"
const res = await fetch(
`http://api.ipstack.com/check?access_key=${this.ipstack_key}`
)
return res.json()
}
private _price(USAprice: number, location: any): number {
const pricePerBurger = USAprice / BigMacIndex["United States"]
let fairPrice = USAprice
if (location.country_name in BigMacIndex) {
fairPrice = Math.round(
pricePerBurger * BigMacIndex[location.country_name]
)
} // ... a few other hacks
return fairPrice
}
async price(USAprice: number, IP?: string): Promise<number> {
const location = await this.ipstack(IP)
const fairPrice = this._price(USAprice, location)
return fairPrice
}
}
You instantiate ParityPrice
with your Ipstack API key (you can get one for free) and call .price()
with the price you're trying to parity match.
bigmac-index-price-calculator then calls Ipstack to geolocate your visitor based on IP, looks up their country in the BigMacIndex and returns the adjusted price.
The full version has some continent-based conversions when a country isn't found and a bit of caching so you can call multiple times without making too many requests. It works pretty well.
RIDICULOUS WARNING 👉 notice the const fetch = require()
part up top? That's because while modern imports work for Jest files and they definitely work when packaging your library, I couldn't get them to work when testing your library. I tried everything. Nothing worked.
Purchasing power parity on reactfordataviz.com
You can see Big Mac Index purchasing power parity in action on https://reactfordataviz-staging.swizec.now.sh/. Scroll down and you'll see a custom price for your location.
Don't click the button yet though. I haven't wired up the adjusted prices with Gumroad yet.
A lot of people have been asking for this and I think it's a great idea. Value based pricing all the way. If learning React Dataviz gets you a $150,000/year job in USA that's very different value than a $50,000/year job somewhere else.
Cheers,
~Swizec
Continue reading about The Big Mac index and Jest fetch testing
Semantically similar articles hand-picked by GPT-4
- Mocking and testing fetch requests with Jest
- How to waste hours of life with fetch() and a bit of brainfart
- Why serverless fits side-projects perfectly
- How to configure Jest with TypeScript
- Is hot dog taco?
Want to become a Fullstack Web expert?
Learning from tutorials is great! You follow some steps, learn a smol lesson, and feel like you got this. Then you go into an interview, get a question from the boss, or encounter a new situation and o-oh.
Shit, how does this work again? 😅
That's the problem with tutorials. They're not how the world works. Real software is a mess. A best-effort pile of duct tape and chewing gum. You need deep understanding, not recipes.
Leave your email and get the Fullstack Web Essays series - a series of curated essays and experiments on modern Fullstack Web development. Lessons learned from practice building production software.
Curated Fullstack Web Essays
Get a series of curated essays on Fullstack Web development. Lessons and insights from building software for production. No bullshit.
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️