ccweerasinghe1994 / NODE-TYPESCRIPT-JEST-TESTING-DOCS-2023

document the node testing learning journy

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2 - Basics of testing with Jest

4 - Section intro

Alt text

5 - Jest introduction

Alt text

Alt text

Alt text

6 - Jest project setup

initiailize a project

npm init -y

it will create a package.json file

{
  "name": "6---jest-project-setup",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

let's install dev dependencies

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

let's create the jest config file

npx ts-jest config:init

let's create a jest config file jest.config.ts

import type { Config } from '@jest/types';

const Config: Config.InitialOptions = {
 preset: 'ts-jest',
 testEnvironment: 'node',
 verbose: true
};

export default Config;

let's make a simple folder structure

Alt text

let's write a simple test

import { toUpperCase } from '../app/Util';

describe('Utils test suite', () => {
 test('should return upper case string', () => {
  const result = toUpperCase('hello');
  expect(result).toBe('HELLO');
 });
});

and add the test script to package.json

  "scripts": {
    "test": "jest"
  },

Alt text

we are getting a warning let's fix it

let's add typescript config file tsconfig.json

tsc --init

now the warning is gone

Alt text

7 - Structure of an unit test

Alt text

import { toUpperCase } from '../app/Util';

describe('Utils test suite', () => {
 it('should return upper case string of a valid string', () => {
  // arrange
  const sut = toUpperCase;
  const expected = 'HELLO';
  // act
  const actual = sut('hello');

  // assert
  expect(actual).toBe(expected);
 });
});

output

Alt text

8 - Jest assertions and matchers

let's create a new function to test

export type TStringInfo = {
 loweCase: string;
 upperCase: string;
 length: number;
 character: string[];
 extraInfo?: object;
};

export function getStringInfo(s: string): TStringInfo {
 return {
  loweCase: s.toLowerCase(),
  upperCase: s.toUpperCase(),
  length: s.length,
  character: s.split('')
 };
}

to compare objects we can use toEqual matcher

to compare primitive types we can use toBe matcher

 it('should return string info of a valid string', () => {
  // arrange
  const sut = getStringInfo;
  const expected = {
   loweCase: 'hello',
   upperCase: 'HELLO',
   length: 5,
   character: ['h', 'e', 'l', 'l', 'o']
  };
  // act
  const actual = sut('hello');

  // assert
  expect(actual).toEqual(expected);

  expect(actual.loweCase).toBe(expected.loweCase);
  expect(actual.loweCase).toHaveLength(expected.length);
  expect(actual.loweCase).toContain<string>('h');

  // array
  expect(actual.character).toEqual(expected.character);
  expect(actual.character).toHaveLength(expected.length);
  expect(actual.character).toContain<string>('h');
  // when u don't know the order
  expect(actual.character).toEqual(
   expect.arrayContaining(['l', 'l', 'o', 'h', 'e'])
  );

  // object

  expect(actual).toMatchObject({
   loweCase: 'hello',
   upperCase: 'HELLO',
   length: 5,
   character: ['h', 'e', 'l', 'l', 'o']
  });

  expect(actual).toHaveProperty('length', 5);
  expect(actual).toHaveProperty('character', ['h', 'e', 'l', 'l', 'o']);
  expect(actual).toHaveProperty('character', expect.arrayContaining(['h']));
  expect(actual).toHaveProperty(
   'character',
   expect.not.arrayContaining(['a'])
  );
  expect(actual.extraInfo).toBeUndefined();
  expect(actual).not.toHaveProperty('extraInfo');
  expect(actual.extraInfo).not.toBeTruthy();
 });

output

Alt text

9 - Multiple tests structure

the above test is a bit long let's refactor it

describe('should return string info of a valid string', () => {
  const sut = getStringInfo;

  // arrange
  const expected = {
   loweCase: 'hello',
   upperCase: 'HELLO',
   length: 5,
   character: ['h', 'e', 'l', 'l', 'o']
  };
  const actual = sut('hello');

  describe('getStringInfo() for argument "hello"', () => {
   it('should return the right lowercase string', () => {
    expect(actual.loweCase).toBe(expected.loweCase);
    expect(actual.loweCase).toHaveLength(expected.length);
    expect(actual.loweCase).toContain<string>('h');
   });

   it('should return the right uppercase string', () => {
    expect(actual.upperCase).toBe(expected.upperCase);
    expect(actual.upperCase).toHaveLength(expected.length);
    expect(actual.upperCase).toContain<string>('H');
   });

   it('should return the right length', () => {
    expect(actual.length).toBe(expected.length);
   });

   it('should return the right character array', () => {
    expect(actual.character).toEqual(expected.character);
    expect(actual.character).toHaveLength(expected.length);
    expect(actual.character).toContain<string>('h');
    // when u don't know the order
    expect(actual.character).toEqual(
     expect.arrayContaining(['h', 'e', 'l', 'l', 'o'])
    );
   });

   it('should return the right info', () => {
    expect(actual.extraInfo).toBeUndefined();
    expect(actual).not.toHaveProperty('extraInfo');
    expect(actual.extraInfo).not.toBeTruthy();
   });
  });
 });

output

Alt text

10 - Parametrized tests

let's write a test for the following function

describe('toUpperCase examples', () => {
    it.only.each([
        {
            input: 'hello',
          expected: 'HELLO'
        },
      {
          input: 'hElLo',
        expected: 'HELLO'
      },
      {
          input: 'HELLO',
        expected: 'HELLO'
      }
      ])('$input should return $expected', ({ input, expected }) => {
          expect(toUpperCase(input)).toBe(expected);
    });
});

output

img.png

  • by doing so we can test multiple cases with a single test
  • reduce the number of tests
  • improve readability
  • improve maintainability
  • improve test coverage
  • and more

3 - Intermediate testing topics

11 - Section intro

Alt text

12 - FIRST principles

Alt text

Alt text

Alt text

Alt text

Alt text

Alt text

13 - Jest hooks

let's add a new class to test

export class StringUtil {
 public toUpperCase(s:string){
  return toUpperCase(s);
 }
}

let's write a test for it

describe.only("StringUtil test suite", () => {
  let sut: StringUtil|null;
  beforeEach(()=>{
   console.log("beforeEach");
   sut = new StringUtil();
  });
  afterEach(()=>{
   console.log("afterEach");
   sut = null;
  });

  it("Should return correct uppercase",()=>{

   const expected = "HELLO";
   // act
   const actual = sut && sut.toUpperCase("hello");
   // assert
   expect(actual).toBe(expected);
  })
 });

these hooks are called beforeEach and afterEach and there context is relative to the place where they are defined.

output Alt text

14 - Testing for errors

let's thow an error in the toUpperCase function

export class StringUtil {
 public toUpperCase(s:string){
  if (!s){
   throw new Error('Invalid input');
  }
  return toUpperCase(s);
 }
}

let's write a test for it

describe.only("StringUtil test suite", () => {
  let sut: StringUtil|null;
  beforeEach(()=>{
   console.log("beforeEach");
   sut = new StringUtil();
  });
  afterEach(()=>{
   console.log("afterEach");
   sut = null;
  });

  it("Should return correct uppercase",()=>{

   const expected = "HELLO";
   // act
   const actual = sut && sut.toUpperCase("hello");
   // assert
   expect(actual).toBe(expected);
  });

  it("Should throw error for invalid input:1",()=>{
   // arrange
   const expected = "Invalid input";
   // act
   const actual = ()=>sut && sut.toUpperCase("");
   // assert
   expect(actual).toThrowError(expected);
  });
  it("Should throw error for invalid input function",()=>{
   // arrange
   function expectError() {
    const actual = sut && sut.toUpperCase("");
   }
   // assert
   expect(expectError).toThrow();
   expect(expectError).toThrowError('Invalid input');
  });

  it("Should throw error for invalid input arrow function",()=>{
   expect(()=>sut && sut.toUpperCase("")).toThrowError('Invalid input');
  });

  it("Should throw error for invalid input try catch block",(done)=>{
   try {
    sut && sut.toUpperCase("");
    done("UpperCase method should throw error for invalid input");
   }catch (e) {
    expect(e).toBeInstanceOf(Error);
    expect(e).toHaveProperty('message', 'Invalid input');
    done();
   }
  });
 });

15 - Jest aliases and watch mode

alt text alt text

xit - skip test xdescribe - skip suite

sit - skip test

we can add --watch to the test script in package.json

  "scripts": {
 "test": "jest --watch"
  },

doing so will run the tests in watch mode.

16 - VSCode debug configuration

alt text

let's create a launch.json file.

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Jest Current File",
            "program": "${workspaceFolder}/node_modules/.bin/jest",
            "args": [
                "${fileBasenameNoExtension}",
                "--config",
                "jest.config.ts"
            ],
            "console": "integratedTerminal",
            "internalConsoleOptions": "neverOpen",
            "disableOptimisticBPs": true,
            "windows": {
                "program": "${workspaceFolder}/node_modules/jest/bin/jest",
            }
        }
    ]
}

then we can run debug.

alt text alt text

17 - Coverage

import type { Config } from '@jest/types';

const Config: Config.InitialOptions = {
 preset: 'ts-jest',
 testEnvironment: 'node',
 verbose: true,
 collectCoverage: true,
 collectCoverageFrom: [
  '<rootDir>/src/app/**/*.ts',
 ],
};

export default Config;

alt text

About

document the node testing learning journy