
This blog is going to have two parts:
- Basics about unit testing, testing frameworks and DOM interaction libraries
- Bursting myths about Jest testing framework. (Yet to come)
As your web application grows with increasing business requirements, in order to maintain the sanity of your front-end code, unit testing is a must. The way we have multiple frameworks in our front-end world, we also multiple testing frameworks and technology. Somebody who is starting with front-end development can easily get confused around all the stuff available. This blog is a small attempt to introduce unit testing in a simpler way. So let’s get started.
Testing Techniques
There are basically two ways using which we can cover the critical functionality of our front-end code.
Unit Testing
In unit testing, we basically test independent units of behaviour in order to ensure that the unit performs the set of specific tasks it is meant to perform. Unit tests are written from a programmer’s aspect. Consider the following analogy: Imagine a building construction site and a building inspector takes a visit. What he will be most concerned about is internal systems of the house, like, framing foundation, electrical, plumbing, etc. He focusses on ensuring that each part of the house is working correctly and safely.
Integration / Functional Testing
In functional testing, tests are written to test an independent piece of functionality, to make sure the system is doing what users are expecting it to. Functional tests are written from the user’s perspective. Let’s see the same building analogy, we talked about above. Imagine the homeowner visits the construction site. His perspective will be entirely different from the building inspector’s perspective. He assumes that internal systems, behave correctly and focusses on what will it be like to live in that house, how the rooms are looking, will it meet family needs and so on.
I hope it gives a clear idea about testing techniques. The scope of this blog is unit testing. So moving forward, let’s talk in detail about the unit as well as the features and benefits of unit testing.
Unit Testing
Let’s go in depth and try to understand what actually a unit is.
Unit
A unit test is an automated piece of code that invokes the unit of work being tested, and then checks some assumptions about a single end result of that unit. A unit test is almost always written using a unit testing framework. It can be written easily and runs quickly. It’s trustworthy, readable, and maintainable. It’s consistent in its results as long as production code hasn’t changed. — Roy Osherove’s The Art of Unit Testing book.
Irrespective of whether we are writing our application in object-oriented programming or in functional programming, a single method in class or module can be considered as a testable unit. A unit is assumed to return a value for a given set of inputs, handles failures or even call another function/method/procedure from within. Let’s consider this example:
// Person class class Person { sayHello (name) { console.log("Hello" + name + "!"); } } // sayHello test cases // - it should print "Hello JS!” if sayHello is called with “JS” => PASSED // - it should print “Hello World!” if sayHello is called with no parameters => FAILED
In the above example, we can easily consider “sayHello” method as a unit and tests its behaviour individually. Considering we are on the same page, let’s move further in understanding what are the things we need to consider where we are writing unit tests.
Unit Test
In order to test different aspects of a unit, we will be required to write various unit tests where each of them focusses on testing one aspect of the unit.
- A unit test should be atomic, i.e., it should be independent of other tests.
- Running one test shouldn’t affect the assertions in another.
- Each unit test can be executed in any order and can be executed repeatedly or concurrently.
This behaviour isn’t hard to achieve if we make sure either our unit tests are not using any shared variable or if they are using it we must be able to reset the value of the shared variable before or after the execution of each test.
Also, a complex unit may have multiple dependencies which it is using in order to complete the task it is meant to do. Consider a react component rendering a form with an input value and a submit button. On submitting the form, an API call is made to save the value of input in the database.
class Form extends React.Component { constructor(props) { super(props); this.state = { input: "", }; this.onChangeHandler = this.onChangeHandler.bind(this); this.onSubmitHandler = this.onSubmitHandler.bind(this); } onChangeHandler(event) { this.setState({ input: event.target.value }); } onSubmitHandler(event) { Axios.post("some_url", {data: this.state.input}) .then(response => { AlertsHelper.show({ type: "success", message: response.message, }); }) .catch(error => { AlertsHelper.show({ type: "error", message: error.message, }); }); } render () { return ( <form onSubmit={this.onSubmitHandler}> <input type=“text” onChange={this.onChangeHandler} /> <button type=“submit”>Submit</button> </form> ); } }
Now, this submit handler is a unit and its task is to make an API call to save data, alert success message on success and error message on failure. This handler is making use of few dependencies like “Axios” library to make an API call, “AlertsHelper” library to show alert messages. But when we are going to write a unit test for this particular unit, we are not supposed to test the behaviour of “Axios” and “AlertsHelper” as these are dependencies used by a unit under consideration. It is not the scope of this unit test to test whether the POST request made it through or whether “AlertsHelper” were able to show an alert message or not. Thus we can mock these dependencies to perform as required and can test different aspects of the unit under consideration.
We can mock Axios.post to be successful in order to test the success case or to be a failure so as to test failure case. We are not concern about what happens in “AlertHelpers.show” but all we care about is that the “onSubmitHandler” should call it with different data based on success and failure case. I hope this gives an idea about how to differentiate dependencies from an actual unit and make unit test independent of the functionality of those dependencies. Moving ahead, let’s talk about unit testing frameworks.
Unit Testing Framework
Testing framework is an execution environment for tests. When we test the behaviour of our application using a source code call unit test, in order to make the application behave the same way as it would have done in a browser, it is required to provide an environment to the application for the execution of tests. This environment is established using a testing framework, which is responsible for the mechanism to drive application under tests.
A testing framework defines the format for expressing expectations, executes tests and generates reports of test results. When we write a unit test for a unit, we make some assertions or expectations on the output of the unit given different conditions. Different frameworks support different assertion libraries as well as reporting tools. Testing framework should be application independent, easy to expand, maintain and perpetuate.
Let’s explore some of the unit testing frameworks. I am going to talk about majorly two of them — Mocha and Jest.
Mocha
It is the most famous and used testing framework in JS world and has a very active community support. As they says, it is “simple, flexible and fun”. It can run tests for both browser as well node applications. It gives user various options in terms of choosing assertion library, mocking library, spies and so on. This gives the user power to create a package based on one’s needs and customise it on the go. When writing tests for React component, you will also need to setup virtual DOM using JSDOM library. Here is a great article that helps you set up mocha for a React application.
Jest
It’s the recommended way of testing React applications developed by Facebook, painless javascript testing framework. It is based on famous Jasmine testing framework which has its own setup for assertions, spies, stubs and mocks. Jest doesn’t give options to select assertion or mock library of your choice but it comes with everything pre-setup. This has its own pros and cons. If you want flexibility and choice in your setup, definitely Jest is not the one but if you need quick setup to get started with writing your tests, Jest is definitely a good option. Moreover, it sets up fake DOM for you too, thus no need for JSDOM library. You can setup Jest using Getting Started guide from Facebook.
The major change that came with Jest and the one I super liked was it offered auto mocking of all the dependencies of your unit. If you remember we talked few minutes before about unit’s unit test should test a unit’s behaviour independent of its dependencies. Now if everything is mocked already, we don’t expect dependencies to run their logic when we are testing the unit and thus we achieve correct behaviour testing of the unit.
Lets understand it with an example:
import {add} from "helpers" const calculator = (a, b, op) => { if (op == "+") return add (a, b); } /***************************************************************** When we unit test calculator, we are concerned with whether calculator calls add helper or not if `op` is `+` and not with whether calculator returns sum of `a` and `b` if op is `+` When we test this with Jest, `add` from helpers is automatically mocked to return undefined and we shouldn't be concerned about it. And as it is auto mocked, we can easliy spy on it as below *******************************************************************/ describe("Calculator", () => { it("should call 'add' helper, if op is '+'", () => { // Given let a = 10; let b = 2; let op = "+"; // When const result = calculator(a, b, op); // Then expect(add).toBeCalledWith(a, b); }) });
DOM Interaction Libraries
Moving ahead, let’s talk a bit about DOM interaction Libraries. As the name suggests, DOM interaction libraries help to access components with given class name, tag name or type. They make it really easy to test React components by simplifying manipulation, traversal or access to elements from React Component’s output.
Facebook provides ReactTestUtils library for DOM interactions. Another great library is Enzyme by Airbnb, which basically use jQuery API for DOM manipulation and traversal. These libraries are independent of test runners, which means we can use any combination — Jest + Enzyme / Jest + TestUtils / Mocha + Enzyme / Mocha + TestUtils. Both libraries support Shallow rendering and Full DOM rendering.
Following is a simple React Component and its test using Jest and Enzyme:
export const Form = ({name, age, onSubmit, onUpdate}) => { return ( <form onSubmit={onSubmit}>
<div className="form-group">
<label className="control-label">Name</label>
<div className="controls">
<input type="text"
className="input-md form-control"
id="name"
name="name"
value={name}
onChange={onUpdate.bind(null, "name")}/>
</div>
</div>
<div className="form-group">
<label className="control-label">Age</label>
<div className="controls">
<input type="number"
className="input-md form-control"
id="age"
name="age"
onChange={onUpdate.bind(null, "age")}/>
</div>
</div>
<div className="controls clearfix">
<button type="submit"
className="btn btn-primary btn-md btn-block">
Submit
</button>
</div>
</form>
); }
// Form.test.js describe('PageForm', () => { let form; const props = { name: 'Xyz', age: 20, onSubmit: jest.fn(), onUpdate: jest.fn() } beforeEach(() => { form = shallow(<Form {...props} />); }); it('should exists', () => { // Given // When // Then expect(form.length).toEqual(1); }); it('should render name input', () => { // Given // When let nameInput = form.find('input[id="name"]'); // Then expect(nameInput.length).toEqual(1); }); it('should render age input', () => { // Given // When let ageInput = form.find('input[id="age"]'); // Then expect(ageInput.length).toEqual(1); }); it('should render submit button', () => { // Given // When let submitBtn = form.find('button[type="submit"]'); // Then expect(submitBtn.length).toEqual(1); }); it('should call "onUpdate" on change of input', () => { // Given // When let nameInput = form.find('input[id="name"]'); nameInput.simulate('change'); // Then expect(props.onUpdate).toHaveBeenCalledWith('name'); }); it('should call "onSubmit" on form submit', () => { // Given // When let submitBtn = form.find('form'); submitBtn.simulate('submit'); // Then expect(props.onSubmit).toHaveBeenCalled(); }); });
Shallow Rendering
Shallow rendering is a concept of rendering component only “one-level” deep which means you do not have access to child component’s behaviour. You make assertions on what render method returns. Child components are not initiated or rendered. Also, it doesn’t need a DOM which makes test quicker to load and run.
Here is a simple Page
Component which renders Form
and Output
components as it’s children.
import React from "react";
import { Form } from './Form'; import { Output } from './Output';
export class Page extends React.Component { constructor(props) { super(props);
this.state = { name: '', age: undefined, output: '', }; }
onSubmit(event) { event.preventDefault(); const {name, age} = this.state; const output = `My name is ${name}. I am ${age} years old.`; this.setState({output}); }
onUpdate(key, event) { const state = this.state; state[key] = event.target.value; this.setState(state); }
render() { const {name, age, output} = this.state;
return (
<div>
<Form name={name}
age={age}
onSubmit={this.onSubmit.bind(this)}
onUpdate={this.onUpdate.bind(this)} />
<Output output={output} />
</div>
);
}
}
When we are writing unit tests for above Page
component, we are only considered to test whether this component is rendering Form
and Output
components or not and the concept of shallow rendering allows us to test this real quick. Below is code for Page.test.js
where I am using Shallow Rendering API of enzyme.
import { shallow } from 'enzyme';
import { Page } from '../../src/components/Page'; import { Form } from '../../src/components/Form'; import { Output } from '../../src/components/Output';
describe('Page', () => { let page;
beforeEach(() => { page = shallow(<Page />); });
it('should exists', () => { // Given
// When
// Then expect(page.length).toEqual(1); });
it('should render Form component', () => { // Given
// When let formCmp = page.find(Form);
// Then expect(formCmp.length).toEqual(1); });
it('should render Output component', () => { // Given
// When let outputCmp = page.find(Output);
// Then expect(outputCmp.length).toEqual(1); });
This is where I am ending this post and soon I will be writing Part2 of the series where I will be bursting different myths about Jest unit testing framework.
Hope this helps and if you like it, please 👏. Thank you so much 😄
Keep dreaming! Keep Learning! Keep Sharing. Have a great day ahead.