Lesson 4
Setting Up and Mastering Testing in TypeScript with Jest
Introduction to Testing Environment Setup

Welcome to our next step in mastering Test Driven Development (TDD) in TypeScript, where we will focus on setting up a robust testing environment. As you might recall, the TDD process involves the Red-Green-Refactor cycle — starting with a failing test, writing the minimum code needed to pass it, and then refining the implementation. In this lesson, we will set up the tools necessary for testing with Jest and ts-jest, guiding you on how to create an efficient testing environment that complements the TDD cycle.

Jest is a popular JavaScript testing framework that integrates smoothly with TypeScript through ts-jest. While other options like Mocha and Chai are available, Jest offers a more straightforward setup, which is why it's our choice for this course. Now, let’s dive into setting up our testing environment in a systematic way.

Installing Jest and TypeScript Testing Essentials

The first step in preparing our environment is installing the necessary libraries. We will use Yarn as our package manager. Here’s how you can do it:

First, install Yarn and TypeScript if you haven't already:
Bash
1npm install -g yarn 2yarn add --dev typescript
Next, you can install Jest, the TypeScript Jest runner, along with the TypeScript runtime and the types for Jest
Bash
1yarn add --dev jest ts-jest ts-node @types/jest
  • jest: The core testing framework.
  • ts-jest: A TypeScript preprocessor for Jest that makes it possible to test TypeScript code without compiling it to JavaScript first.
  • ts-node: Allows the execution of TypeScript code without pre-compilation.
  • @types/jest: Type definitions for Jest, ensuring proper TypeScript support.

Remember, on CodeSignal, these libraries are pre-installed, so you can focus on writing and running tests without worrying about setup.

Creating the Jest Configuration

To utilize ts-jest with TypeScript, a configuration file is essential. Let’s create a jest.config.ts file with the following content and place it in the project root:

TypeScript
1import type {Config} from '@jest/types'; 2 3const config: Config.InitialOptions = { 4 transform: { 5 "^.+\\.tsx?$": "ts-jest", 6 }, 7}; 8export default config;
  • The transform property tells Jest to use ts-jest for processing .ts files, ensuring TypeScript compatibility.
  • This setup simplifies test writing and running, maintaining focus on the TDD process: Red-Green-Refactor.

Other configuration methods exist, but this one offers a streamlined approach tailored for our TypeScript requirements.

Running Tests in Watch Mode

With the setup complete, running tests becomes straightforward. Use the following command to execute all tests:

Bash
1yarn jest

For continuous feedback during development, use the watch mode:

Bash
1yarn jest --watchAll

This mode automatically re-runs tests upon file changes, enhancing productivity and aligning with the TDD philosophy: quick feedback and iterative improvements.

Examples of patterns with Jest

With the environment ready, let's look at a test suite. We’ll utilize a User class example:

Using `describe` for Nesting and Grouping

The describe block in Jest is used to group related tests together, enhancing the structure and readability of your test suite. By nesting describe blocks, you can create a hierarchical organization that mirrors the structure of your code. This is especially useful for complex modules with multiple aspects to test.

TypeScript
1describe('User', () => { 2 describe('Initialization', () => { 3 it('Creates Users correctly', () => { 4 // test logic 5 }); 6 }); 7 8 describe('User email tests', () => { 9 test('getEmail returns correct email', () => { 10 // test logic 11 }); 12 13 test('email contains @ symbol', () => { 14 // test logic 15 }); 16 }); 17});
`it` vs `test`

In Jest, it and test functions are interchangeable and serve to define individual test cases. The choice between the two often boils down to personal preference or aligning with stylistic conventions:

  • it: Tends to be used when writing tests in a behavior-driven development (BDD) style, where tests read more like sentences (e.g., it('does something')).
  • test: Preferred when using a more straightforward language, as it's a direct term matching the action of performing a test.

Ultimately, both achieve the same result. Select the one that best fits the readability and consistency standards of your codebase.

Setup with `beforeEach`

The beforeEach function is used to run a specific block of code before each test in a describe block. This ensures consistent setup, eliminating repetitive code and maintaining test independence.

TypeScript
1beforeEach(() => { 2 user = new User('Jane Doe', 'jane@example.com'); 3});
Matchers with `toBe`, `toEqual`, `toBeInstanceOf`, `toContain`

Jest provides various matchers for assertions:

  • toBe: Checks strict equality.
  • toEqual: Compares the equality of objects or arrays by checking their contents rather than reference or strict identity.
  • toBeInstanceOf: Verifies that an object is an instance of a class.
  • toContain: Ensures an item is in an array or a substring is present in a string.
TypeScript
1expect(user.getName()).toBe('Jane Doe'); 2expect(user).toEqual({ name: 'Jane Doe', email: 'jane@example.com' }); 3expect(user).toBeInstanceOf(User); 4expect(user.getEmail()).toContain('@');
Async Tests

To handle asynchronous code, Jest uses async functions combined with await. Tests can return promises or use the done callback to indicate completion.

TypeScript
1describe("fetchUser", () => { 2 it('fetches data asynchronously', async () => { 3 const user = await fetchUser(); 4 expect(user).toEqual({ 5 name: 'John Doe', 6 email: 'john@example.com', 7 }); 8 }); 9});
Testing Code with Exceptions

Jest’s .toThrow matcher is used for testing code that should throw an exception. It can be combined with a specific error message or class.

TypeScript
1test('invalid email throws an error', () => { 2 expect(() => { 3 new User('Invalid', 'invalid-email'); 4 }).toThrow('Invalid email'); 5});
Putting it all together!
TypeScript
1import {User, fetchUser} from "../src/user"; 2 3describe('User', () => { 4 let user: User; 5 6 // Setup - runs before each test 7 beforeEach(() => { 8 user = new User('Jane Doe', 'jane@example.com'); 9 }); 10 11 it('Creates Users correctly', () => { 12 expect(user.getName()).toBe('Jane Doe'); 13 expect(user.getEmail()).toBe('jane@example.com'); 14 expect(user).toBeInstanceOf(User); 15 }); 16 17 describe('User email tests', () => { 18 test('getEmail returns correct email', () => { 19 expect(user.getEmail()).toBe('jane@example.com'); 20 }); 21 22 test('email contains @ symbol', () => { 23 expect(user.getEmail()).toContain('@'); 24 }); 25 }); 26 27 test('invalid email throws an error', () => { 28 expect(() => { 29 new User('Invalid', 'invalid-email'); 30 }).toThrow('Invalid email'); 31 }); 32 33 describe("fetchUser", () => { 34 it('fetches data asynchronously', async () => { 35 const user = await fetchUser(); 36 expect(user).toEqual({ 37 name: 'John Doe', 38 email: 'john@example.com', 39 }); 40 }); 41 }); 42});
Summary and Next Steps

In this lesson, we've successfully set up a TypeScript testing environment using Jest and ts-jest. Key accomplishments include:

  • Installation and Configuration: Acquired the necessary libraries and created a streamlined Jest configuration to integrate smoothly with TypeScript.
  • Execution: Learned how to run tests in both standard and watch modes to facilitate continuous testing.

We also explored various Jest patterns to enhance testing:

  • describe for Grouping: Organizes tests hierarchically for better readability.
  • it vs test: Both define test cases; choice depends on stylistic preference.
  • beforeEach: Runs common setup code before each test, promoting DRY principles.
  • Matchers like toBe, toBeInstanceOf, toContain: Used for assertions such as equality checks, instance verification, and substring presence.
  • Async Tests: Uses async/await for testing code involving promises.
  • Exception Testing with .toThrow: Validates that expected exceptions are triggered.

With this groundwork in place, you're prepared to dive into practice exercises focusing on crafting tests with Jest. These exercises will deepen your understanding of Jest's features and improve your ability to write clear and effective tests. The upcoming unit will bring us back to TDD, building upon the skills you hone in these practical sessions.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.