Introduction to unit testing Javascript applications (Part 1)

Amandeep Singh
Dineout Tech
Published in
6 min readMar 30, 2022

--

Testing is one of the most important aspects of modern software development and this helps in building a piece of software that is maintainable and bug-free.

Writing unit tests for code might not seem like a big benefit for the project at the initial stages as writing unit tests takes time. But often, when projects start growing in complexity, the maintenance of a project becomes a huge challenge. Fixing a small bug in one user flow introduces a new bug and breaks another user flow. The developers are focused on solving the problem/bug and often miss to manually test all the related scenarios, as a result, regression bugs often get missed during the development and QA phases and are deployed to production.

All the above scenarios can be avoided by writing tests for code. If the unit tests are well written for critical pieces of code, any new bug created can be caught at a very early stage during development and can be fixed.

The tests give developers the confidence to ship bug-free applications to production and enable them to focus on the project roadmap.

Types of testing:

Writing automated test cases or an application can be categorized broadly into 4 categories:

  1. Static testing: Static testing refers to checking code for bugs (typos and type errors) during the process of writing code. Javascript is a loosely typed language and type errors are not caught at compile time. Only while running the code, type errors are thrown. The type errors can be avoided by using a static type check mechanism for javascript (Typescript, Flow etc). Typescript/Flow give a mechanism to associate types to variables/ function parameters and thus can throw an error at compile-time in case there is an error/ typo in code.
  2. Unit Testing: As the name suggests, unit testing means testing the small units (functions) in isolation. Unit tests ensure that the individual functions/components are doing what is expected out of them.
  3. Integration testing: Integration tests are written to ensure that different parts of the application are integrated as expected and work together without any issues.
  4. End to End testing (E2E): This type of testing covers the testing of the application from a user perspective. That means testing the application as it is meant to be used by an end-user.

Let’s consider the below example to highlight the difference between various types of testing:

Consider building a calculator application using react.js. The UI consists of various buttons for digits 0 to 9 and operations like +, —, *, / and =. The application has functions for each operation addition, subtraction, multiplication, and division.

The above operations can be considered as the heart of the application if there is a bug in any of these functions, the whole application will cease to work correctly.

For unit testing, the above functions should be tested in isolation so as to ensure the functions give the correct output for the given set of input.

Integration testing can be done here to ensure that the above functions are called correctly from the components calling them. E2E testing shall be performed to ensure that the user is displayed the correct result of the operation. E2E testing shall be performed from the user’s perspective and shall be carried out in a way that the test shall have no knowledge of the implementation details of the application.

Testing javascript Applications:

For testing javascript applications, a number of tools (test runners and assertion libraries are available). Let’s first define what Test runner and assertion means.

Test runners: These are the frameworks that enable us to execute the tests written for javascript applications. Example: Jest, Moca, Jasmine, Karma.

Assertion Libraries: Assertion libraries enable us to assert the correctness of the test output in a readable manner and without writing a lot of if statements. Example: Chai, Node.js Assert.

An assertion is basically a statement that shall throw an exception in case the assertion fails and thus causing the given test to fail.

Consider the below example:

Here the expect (assertion included in jest framework) will not throw an error and hence the test shall pass successfully.

Now say the function definition was changed to:

In this case, expect(add(1,2)).toBe(3) will throw an error and hence the test will fail to enable the developer to know that something is wrong in the code.

Introduction to jest: Jest is a testing framework maintained by Facebook and is recommended by reacting for testing react.js applications. Jest is really easy to set up and comes with an assertion library and framework for mocking and spying.

Link to jest website: https://jestjs.io/

Introduction to describe and test blocks

Describe: Describe block is used to group together a set of related tests.

Test: The test block contains the actual code that is executed by jest.

Example:

Basic Jest setup:

To set up a jest in your project, The first step is to install jest to the project. Use the below command:

npm install — save-dev jest

Add a script to package.json:

And that’s it. The setup for jest is complete! By doing just 2 steps, jest is set up in a project with the default configuration.

Writing the first test:

Let's create a file index.js with the following code:

And a test file index.test.js:

Now run the npm test, you should see the below output:

And with that, we have run our first test.

Jest is configured to run any file with extension *.spec.js, *.test.js or any file in a directory named __tests__. This can be customized with a jest config.

Assertions in jest:

Jest contains a number of assertions and the link to official documentation is below: https://jestjs.io/docs/en/expect

Configuring jest:

Jest can be configured in 2 ways:

  1. Adding the configuration in package.json
  2. Adding the configuration in a javascript file: jest.config.js

For a complete list of configuration options refer to the jest documentation: https://jestjs.io/docs/en/configuration

Writing unit test cases for critical pieces of code have enabled us, at Dineout to catch code bugs at a very early stage in developing new requirements and save QA cycles and hence reducing the delivery time and ensuring the building of a quality product. There are other benefits of unit testing like writing testable code that ensures that the functions do only what they are supposed to do (Single responsibility principle). Writing unit tests also give a very clear picture of the quality of code. As a rule of thumb, if unit testing a code is complex, it means that the code itself is complex and could be broken down into smaller functions / revamped in a better way. Also, unit tests should be written in such a way that a code refactor shall not break the test. That means the unit tests shall not test/assert on the implementation of function rather at the input/output of the same.

These details along with a practical deep dive into writing unit tests will be covered in subsequent parts of this blog series.

--

--