Testing TypeScript Apps Using Jest

Learn via video courses
Topics Covered

Overview

Testing is an important phase of Software Engineering. At the time of testing, we determine the expectations and test cases for our application and apply to check whether they are passed or not. The purpose of software testing is to identify errors, gaps, or missing requirements as compared to the actual requirements. Software testing comprises verification and validation. Testing is required because software bugs can be expensive or even dangerous.

Introduction

Jest TypeScript is a JavaScript testing framework with a focus on simplicity. We can use Jest with projects built on Babel, TypeScript, NodeJs, ReactJs, Angular, etc. Jest offers a test runner, assertion library, CLI tool, and great support for various mocking techniques. It comes with an interactive mode that automatically runs all affected tests for the code changes that you've made in your last commit. It provides syntax to test a single test or skip tests with .only and .skip. This feature can be useful when we are required to debug individual tests.

Pre-requisites

Before proceeding to TypeScript jest testing, we should have installed the following on your local machine :

  • Git
  • NodeJs
  • A JavaScript package manager
  • An IDE or text editor like Visual Studio Code or Sublime text editor

Also, we should have prior knowledge of Sequelize which is an easy-to-use promise-based Node.Js ORM tool used for MySQL, MariaDB, Snowflake, and Microsoft SQL Server.

Setting Up Test Dependencies

To set up the test dependencies, first, we need to install our test libraries as development dependencies.

After running the above command, we will configure jest TypeScript for our app by creating a new file named jest.config.ts and adding the following snippet in it :

There are furthermore options to create a jest TypeScript configuration such as running jest --init that eventually creates a jest.config.js file with default configurations or adding the configurations in package.json.

We will add a yarn command to run the tests in the script section of the package.json file.

When we have configured our jest library, we define some sort of structure for our tests.

Setting Up Project Test Structure

We will focus majorly on two types of functional tests in our project :

  • Unit Tests :
    This kind of test majorly focuses on activities of specific components without having a check on the other components i.e. mocking external components as we can test a component on our own.
  • Integration Tests :
    This kind of test majorly focuses on how the components of the application interact with each other to achieve expected results i.e. we test the results of services, routes, and data access layers (DALs).

Functional testing concentrates on the results and functionality of the application. Now, let's create our tests directory with folders for integration tests and unit tests.

Implementing Jest's concepts and Test Functions

As we have created a structure for our tests, now we will go through the concepts of Jest TypeScript and understand their usage in the project.

Setup and Teardown

This refers to the task that we are required to perform to get our app in the necessary state for testing, like populating or clearing tables. Jest can perform these tasks through various functions like beforeAll, beforeEach, afterAll, and afterEach.

As the names suggest, beforeAll, and afterAll are used to run some tasks before and after the other tasks in a particular scope, but beforeEach and afterEach are used to run tasks before and after each test within their scope.

The word scope here depicts the portion of these test functions, for example, when beforeAll is declared within a describe block that defines a group of related tests further applied at all of the given tests inside our block.

Now, let's test by writing tests for our ingredient DAL, where we will shorten our ingredient table and create some ingredient table entries for testing that is needed before running all the tests.

Firstly, we will create a dal directory in tests/integrations, then a test file is added named ingredient.test.ts.

In the ingredient DAL test above, a function is defined as dbTeardown that we will call in beforeAll and afterAll of the root describe. This depicts that it will run before all the tests are executed, and once again after all the tests are executed.

We should consider the fact that some of the tests cover methods that runs queries on our database. We should use a separate database exclusively for testing to prevent distorted production or development data.

In this project, we will perform this by defining in the .env with a variable named TEST_DB_NAME, which will be passed to our Sequelize instance during test runs like this :

Jest TypeScript sets the NODE_ENV environment variable during the test execution to have a test value.

Understanding Matchers in Jest

Matchers is defined as the functions of Jest TypeScript that are used to test the values produced in our test. Essentially, matchers are referred to as the functions we append to expect(), like toEqual and toBeNull.

For example, in the file ingredient.test.ts, we wrote the tests to cover the findOrCreate method in which we were expected to return an existing entry with the same name without updating the existing entry.

Here, The expect function returns an expected object that Jest calls. We can call matchers on the to expect objects to declare an expected value.

Testing Routes and HTTP Requests with Jest and SuperTest

We can test the routes that are defined in our API using Jest and Supertest. We need to use Jest and SuperTest to test our /recipes routes. First, Supertest will be installed by running the following command :

Now, let's create an instance of a Supertest request agent to call our application's routes. To achieve this, we will create a helper file that creates a single Supertest agent instance and shares the file with all the tests.

SuperTest grabs our express application instance as a parameter, that we have retrieved from our src/index.ts.

Now, we will create a folder named routes in tests/integrationadd the test file in the folder and rename the file as recipe.test.ts.

Testing with Mock Objects

We have already talked about the unit tests that test a single component calls when ignoring the external files or the services. To perform this operation effectively, we used to create mock objects that replace those external files and services.

Let's illustrate this by writing tests for our review service while mocking the DAL method. First, we will create a services directory under the unit tests directory, and add our test file review.test.ts into it.

Through this test, we will test the publish method of the review service and mock the review DAL's update method. When we use mocks, we are sure that the external methods are called the number of times and the ways we expect.

Spying and Overwriting Object Attributes

We can spy on and overwrite the implementation of an object's attribute with the help of jest TypeScript. Let's take an example, in our Review Series Unit Test, we spy on the Date class, which is an attribute of Node's global object.

In the above test, we have used jest.spyOn to overwrite the date class and define a mock implementation of the class's constructor to return a particular value at every time. We should always remember to add mockRestore on classes when we overwrite with jest.spyOn.

Running Specific Tests or Test Groups

Jest TypeScript provides the capability to run or skip specific tests and test groups that uses the functions only and skip. When we use these test groups, they look like the following :

  • describe.only('xxxx')
  • describe.skip('xxxx')
  • test.only('xxx')
  • it.only('xxxx')
  • it.skip('xxxx')

We can run a single test file by adding its path in the test run command.

Using Jest to Check Test Coverage

We can use jest TypeScript to check our test coverage by adding --coverage to the test script in package.json.

The Jest coverage shows how much our application is covered in our tests using metrics per file. The coverage metrics are as follows :

  • Statements Coverage (% Stmts) :
    This shows the percentage of statements that the tests execute when the test is running.
  • Branch Coverage (% Branch) :
    This shows the percentage of control structure paths that are executed when the test is running.
  • Function Coverage (% Funcs) :
    This shows the percentage of functions executed per file when the test is running.
  • Lines Coverage (% Lines) :
    This shows the percentage of the lines of code that executes when the test is running.
  • Uncovered Line Numbers (Uncovered Line [#s]) :
    This shows the specific lines that are uncovered in every line when the test is running.

uncovered-line-numbers

Jest uses color codes of each of the above metrics using three colors :

  • Red is used to depict poor coverage
  • Yellow is used to depict average coverage
  • Green is used to depict good coverage

Conclusion

  • Jest TypeScript is a lightweight and simple testing framework that provides us with a variety of testing capabilities for JavaScript and TypeScript projects.
  • Jest TypeScript provides various functionalities like running tests in parallel, mocking, assertions, and prioritizing failed tests.
  • To test a TypeScript app using Jest, we majorly focus on two types of functional tests :
    • Unit tests and
    • Integration tests.
  • ts-jest is a Typescript preprocessor of Jest that allows us to test projects written in TypeScript.
  • We can use three given commands to run our tests : npm run test, npm t, and npm test.