Real-life examples of Test Driven Development with Jest

A few scenarios that we find in all projects.

Jyoti K Nanda
Jul 20 · 6 min read
Image for post
Image for post
Photo by Marvin Meyer on Unsplash

Testing the Try-Catch

The try-catch is the most commonly used statement in our projects and we must test them.

Let’s take an example that will parse JSON data and throw error, when data is invalid.

// @file validation.jsexport class InvalidDataException {
constructor() {
this.message = "invalid data";
this.name = "InvalidDataException";
}
}
export const validateData = (data) => {
try {
if (data) {
const mockData = JSON.parse(data);
return { ...mockData };
} else {
throw new InvalidDataException;
}
} catch (e) {
if (e instanceof InvalidDataException) {
throw (e);
}
return e;
}
};
// @file validation.test.jsimport { InvalidDataException, validateData } from “./validation”;describe(“test validateData”, () => {
it(“should return valid data”, () => {
const data = JSON.stringify({ "id": "1234" });
expect(validateData(data)).toMatchObject({ id: "1234" });
});
it(“should throw and catch error”, () => {
const errObj = validateData(“hello world”);
expect(errObj instanceof Error).toBe(true);
const validate = () => {
validateData();
};
expect(validate).toThrowError(InvalidDataException);
expect(validate).toThrowError(new Error(“invalid data”));
expect(validate).toThrowError(“invalid data”);
});
});

mock() Vs. doMock()

Hoisting
babel-plugin-jest-hoist: The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.

doMock() is not hoisted to the top unlike mock(), meaning doMock() has access to variables in the top.

Works with jest.resetModules()
Mocked modules need to be reset when working with doMock().

Usage

Mocking public modules

Let’s say we want to perform a specific task based on a certain browser or platform and use public module that helps in the detection.

// @file style-utils.jsimport { Platform } from "react-native";export const getFontSize = () => {
if (Platform.OS === "android") {
return { fontSize: 14 };
} else {
return { fontSize: 16 };
}
}
// @file style-utils.test.jsjest.mock("react-native", () => ({
Platform: {
OS: "android"
}
}));
import { getFontSize } from "./style-utils";describe("Mocking Platform", () => {
it("should return fontSize for Android", () => {
expect(getFontSize()).toMatchObject({ fontSize: 14 });
});
});

Please note that, jest.mock() of modules can be in a separate file, which is then used in setupFiles: [] of Jest config.

A simple use of mockImplementation()

We all love API’s and in the web world we have Web API’s. Let’s say we have an API that returns a list of items.

// @file apis.jsimport axios from "axios";export const getEmpList = () => axios.get("https://run.mocky.io/v3/f26083fa-a253-48a8-8c4c-cf6bf1b1f98f")
.then(res => res.data)
.catch(e => e);

For HTTP requests we have axios module and mock API (mocky.io) that will return 3 items.

// @file apis.test.jsimport { getEmpList } from "./apis";describe("Test Fetch API", () => {
beforeEach(() => {
jest.resetModules();
});
it("should return 3 items", async () => {
const response = await(getEmpList());
expect(response.length).toBe(3);
});
it("should return success response", async () => {
const { getEmpList } = require("./apis");
jest.mock("axios");
const axios = require("axios");
const mockResponse = { data: { greet: "Hello World" } };
axios.get.mockImplementation(() => Promise.resolve(mockResponse));

const res = await (getEmpList());
expect(res).toMatchObject({ greet: "Hello World" });
});
it("should return error response", async () =>{
jest.mock("axios");
const axios = require("axios");
axios.get.mockImplementation(() => Promise.reject(new Error("something went wrong"))); const { getEmpList } = require("./apis");
const err = await (getEmpList());
expect(err.message).toBe("something went wrong");
});
});

Please note that we can also achieve the above test with doMock().

Mocking environment config

Every application uses environment properties or variables. We may use these properties to render a webpage or screen with a different language, switches to enable or disable a feature, setting timeouts for API calls etc.

// @file envConfig.jsexport const envConfig = {
"enable_feature_A": "false",
"enable_feature_B": "true",
"locale": "en-US",
"country": "US"
};

Here is a sample environment config and a function that returns the value of the config variable.

// @file config-utils.jsexport const getConfig = (envProp) => {
if (typeof envProp === "string") {
if (envConfig[envProp] === "true") {
return true;
} else if (envConfig[envProp] === "false") {
return false;
} else {
return envConfig[envProp];
}
}
};

Testing the environment config.

// @file config-utils.test.jsdescribe("test getConfig", () => {
beforeEach(() => {
jest.resetModules();
});
it("should return undefined", () => {
const { getConfig } = require("../config-utils");
expect(getConfig(123)).toBe(undefined);
});
it("should return false for feature_A switch", () => {
jest.doMock("./envConfig", () => ({
envConfig: {
"enable_feature_A": "false"
}
}));
const { getConfig } = require("./config-utils");
expect(getConfig("enable_feature_A")).toBe(false);
});
it("should return true for feature_B switch", () => {
jest.doMock("./envConfig", () => ({
envConfig: {
"enable_feature_B": "true"
}
}));

const { getConfig } = require("./config-utils");
expect(getConfig("enable_feature_B")).toBe(true);
});
});

Let’s say we have a component called App, a function that returns some data based on the feature-check using getConfig.

// @file app.jsimport { getConfig } from "../getConfig";export const app = () => {
if (getConfig("enable_feature_A")) {
return "Hello feature A";
} else {
return "Hello World !!";
}
};

Finally testing the app component.

// @file app.test.jsjest.mock("../config-utils");import { getConfig } from "../config-utils";describe("test app", () => {
it("should render feature A", () => {
getConfig.mockReturnValue(true);
const { app } = require("../app");
expect(app()).toBe("Hello feature A");
});
it("should render hello world", () => {
const { app } = require("../app");
getConfig.mockReturnValue(false);
expect(app()).toBe("Hello World !!");
});
});

Tips & Tricks

Coverage
We all know how to configure Jest coverage. But the non-GUI report at times not so informative and doesn’t give an insight to the line of code covered vs. not covered.

Image for post
Image for post

jest --coverage creates a folder in the root called coverage and sub-folder Icov-report hosts index.html. This is the HTML formatted coverage, which can be viewed, opening in a browser.

What doesI represent in coverage?

Image for post
Image for post

What does E represent in coverage?

Image for post
Image for post
Image for post
Image for post

Partially covered

Image for post
Image for post

What is an ideal coverage threshold?

100%? I know I am being optimistic but I believe anything above 85% should be good.

That’s it!

I would like to thank you for taking some time and reading this article. I have always believed that there is a scope to do things in a better way. Hence, I would really appreciate if you leave your comments/suggestions.

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store