Page Content

Tutorials

How do you Test in TypeScript? With Example

Test in TypeScript

The integration of TypeScript (TS) with popular JavaScript (JS) testing frameworks is streamlined through tools that handle the necessary compilation and source mapping, allowing developers to write tests in TS while leveraging established JS ecosystems. Testing and debugging strategies largely revolve around two core techniques: transpiling TS to JS and using source maps, or running TS directly using tools like ts-node.

Why Checking and Testing is Necessary

The need for rigorous checks is fundamentally driven by the nature of traditional JavaScript. JavaScript is an interpreted language that requires the code to be executed (run) to determine its validity. If errors are present, a developer might complete the code only to receive no output or unexpected behaviour, necessitating potentially hours spent tracking down bugs because issues are typically surfaced only at runtime.

JavaScript’s freedom and dynamic typing, while flexible, become a disadvantage when safety is required, as the language avoids exceptions wherever possible instead of alerting the developer to invalid operations. This delay between making a mistake and discovering it often when the program is already running or deployed is the reason TypeScript was created.

The Role of TypeScript in Error Checking

TypeScript is often described as JavaScript plus an optional static type system. Its primary motivation is to introduce static typing, enabling developers to catch errors at compile time rather than having to wait for them to crash the system at runtime.

This compile-time error checking, provided by the TypeScript transpiler, highlights errors before the script is run. The type checker verifies that the code is type-safe, catching issues such as calling a method on an object when that method doesn’t exist, or calling a function with an argument of the wrong type.

The compiler catching errors is preferable to having things fail at runtime. Furthermore, TypeScript provides instant feedback, displaying error messages directly in the text editor as the code is typed.

Benefits of Static Analysis

Moving error detection earlier into the development cycle yields significant advantages, effectively reducing the reliance on purely runtime unit tests to catch basic type errors:

  • Safety and Reliability: The type system makes programs safer by preventing them from performing invalid operations, eliminating entire classes of type-related bugs. Static typing also makes code easier to debug and maintain compared to standard JavaScript.
  • Code Quality and Documentation: Types enhance code quality and documentation. Explicitly declaring types makes the codebase self-descriptive, making it easier for developers to understand the expected data types for function parameters and returns.
  • Refactoring and Tooling: TypeScript allows editors and tools to perform automated refactors that are type-aware, enhancing developer productivity. By enforcing usage restrictions, TypeScript ensures that changes in one area do not break other parts of the code that rely on it.

Testing with TypeScript and JavaScript Frameworks

You can utilise TypeScript code with any JavaScript testing framework, and before you run tests, you can always do a basic TS-to-JS transform. But some technologies improve the development experience by doing the translation automatically.

Jest Configuration ()

Jest is a popular framework for unit testing with good TypeScript support. Ts-jest, a TypeScript preprocessor for Jest, is the main tool for using Jest with TypeScript.

Installation

Installing Jest, the TypeScript definitions for Jest, and the ts-jest preprocessor are typically required for the setup as development dependencies:

npm i jest @types/jest ts-jest typescript -D

Debugging and correct reporting against the original .ts code are made possible by ts-jest, which plays a vital role in enabling Jest to transpile TypeScript files on the fly with ease and by providing built-in source map support.

Jest Configuration Details

Jest needs to be configured; this is usually done directly in package.json or through a jest.config.js file. All TypeScript files should typically be kept in a src folder for a tidy project setup.

A simple configuration setup could resemble this (using the package.json or jest.config.js compatible format):

// package.json snippet demonstrating Jest configuration
{
  "jest": {
    "roots": [
      "<rootDir>/src"
    ],
    "transform": {
      "^.+\\.tsx?$": "ts-jest"
    },
    "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx",
      "json",
      "node"
    ]
  }
}

An explanation of the main options

  • "roots": Specifies the directory Jest should search for files (e.g., assuming files are in src/).
  • "transform": Instructs Jest to use ts-jest to handle files ending in .ts or .tsx. This is how ts-jest integrates to perform on-the-fly transpilation. In older Jest versions, the transform value might point directly to <rootDir>/node_modules/ts-jest/preprocessor.js.
  • "testRegex": Defines the naming conventions Jest uses to locate test files (e.g., files in __tests__ folders, or files ending in .test or .spec).

Jest Example (Code and Output)

Examine a straightforward utility function in src/foo.ts:

// src/foo.ts
export const sum
= (...a: number[]) =>
a.reduce((acc, val) => acc + val, 0); 

And the corresponding test file in src/foo.test.ts:

// src/foo.test.ts
import { sum } from '../src/foo'; 

test('basic sum test', () => { 
    expect(sum()).toBe(0); 
});
test('basic again', () => {
    expect(sum(1, 2)).toBe(3); 
});

To run the tests, you execute npx jest or npm test (if configured with "test": "jest" in package.json).

Example Output:

PASS  ./foo.test.ts
  ✓ basic sum test (3ms)
  ✓ basic again (3ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.46s, estimated 2s
Ran all test suites.

Jest also supports running tests with code coverage generation using jest --coverage and requires an optional configuration line for TypeScript coverage reporting: "testResultsProcessor": "<rootDir>/node_modules/ts-jest/coverageprocessor.js".

Testing with tape

The TAP-compliant markup output of the simple JavaScript testing framework Tape is well-known. With TypeScript, it is simple to use by utilising ts-node for execution.

Installation and Usage

When using tape, the framework and associated types must be installed, along with the ts-node globally:

npm install --save-dev tape @types/tape 
npm install -g ts-node 

Example test in math.test.ts:

// math.test.ts
import * as test from "tape"; 

test("Math test", (t) => { 
    t.equal(4, 2 + 2); // assertion 1
    t.true(5 > 2 + 2); // assertion 2
    t.end();
});

The test is executed directly using ts-node:

ts-node node_modules/tape/bin/tape math.test.ts 

Example Output:

TAP version 13
# Math test
ok 1 should be equal
ok 2 should be truthy

1..2
# tests 2
# pass  2
# ok

Testing with Cypress

Cypress is a well-liked End-to-End (E2E) testing tool. Cypress has the major benefit of coming pre-configured with TypeScript definitions.

When setting up Cypress for a TypeScript project, it is recommended to create a separate e2e directory. This strategy helps avoid dependency conflicts and prevents the testing framework from polluting the global namespace (with functions like describe, it, and expect), which could cause conflicts with the project’s main tsconfig.json or node_modules.

Cypress also provides a beneficial interactive Google Chrome debug experience. Debugging Cypress tests can involve using a debugger statement in application code, or utilizing the .debug() command provided by Cypress in test code to pause execution.

Index