We use cookies to enhance your experience on the site
CodeWorlds

Testing TypeScript with Jest

TypeScript adds a type system to JavaScript, but tests still verify behavior at runtime. In Jurassic Park, TypeScript is like species documentation - it tells us what to expect, but tests verify whether the dinosaurs actually behave that way.

Configuring ts-jest

To test TypeScript with Jest, we need

ts-jest
:

1npm install --save-dev ts-jest @types/jest
1// jest.config.js
2module.exports = {
3  preset: 'ts-jest',
4  testEnvironment: 'node',
5  testMatch: ['**/*.test.ts'],
6};

Testing Interfaces and Types

1// dinosaur.ts
2interface Dinosaur {
3  name: string;
4  species: string;
5  health: number;
6  diet: 'carnivore' | 'herbivore' | 'omnivore';
7}
8
9function createDinosaur(
10  name: string,
11  species: string,
12  diet: Dinosaur['diet']
13): Dinosaur {
14  return {
15    name,
16    species,
17    health: 100,
18    diet,
19  };
20}
21
22// dinosaur.test.ts
23describe('createDinosaur', () => {
24  it('should return object matching Dinosaur interface', () => {
25    const dino = createDinosaur('Rex', 'T-Rex', 'carnivore');
26
27    // TypeScript checks types at compile-time
28    // Tests check values at runtime
29    expect(dino).toEqual({
30      name: 'Rex',
31      species: 'T-Rex',
32      health: 100,
33      diet: 'carnivore',
34    });
35  });
36
37  it('should have health as number', () => {
38    const dino = createDinosaur('Blue', 'Velociraptor', 'carnivore');
39    expect(typeof dino.health).toBe('number');
40    expect(dino.health).toBeGreaterThanOrEqual(0);
41    expect(dino.health).toBeLessThanOrEqual(100);
42  });
43});

Testing Generic Functions

1// repository.ts
2interface Repository<T> {
3  items: T[];
4  add(item: T): void;
5  findById(id: string): T | undefined;
6  getAll(): T[];
7}
8
9function createRepository<T extends { id: string }>(): Repository<T> {
10  const items: T[] = [];
11
12  return {
13    items,
14    add(item: T) {
15      items.push(item);
16    },
17    findById(id: string) {
18      return items.find(item => item.id === id);
19    },
20    getAll() {
21      return [...items];
22    },
23  };
24}
25
26// repository.test.ts
27interface TestDino {
28  id: string;
29  name: string;
30  dangerous: boolean;
31}
32
33describe('Repository<T>', () => {
34  let repo: Repository<TestDino>;
35
36  beforeEach(() => {
37    repo = createRepository<TestDino>();
38  });
39
40  it('should add and retrieve items', () => {
41    const dino: TestDino = {
42      id: '001',
43      name: 'Rex',
44      dangerous: true,
45    };
46
47    repo.add(dino);
48
49    expect(repo.getAll()).toHaveLength(1);
50    expect(repo.findById('001')).toEqual(dino);
51  });
52
53  it('should return undefined for missing item', () => {
54    expect(repo.findById('999')).toBeUndefined();
55  });
56
57  it('should return copy of items array', () => {
58    repo.add({ id: '001', name: 'Rex', dangerous: true });
59
60    const items = repo.getAll();
61    items.push({ id: '002', name: 'Blue', dangerous: true });
62
63    // Original array unchanged
64    expect(repo.getAll()).toHaveLength(1);
65  });
66});

Testing TypeScript Classes

1// enclosure.ts
2class Enclosure {
3  private dinosaurs: Dinosaur[] = [];
4
5  constructor(
6    public readonly name: string,
7    public readonly capacity: number,
8    public readonly securityLevel: number
9  ) {}
10
11  addDinosaur(dino: Dinosaur): void {
12    if (this.dinosaurs.length >= this.capacity) {
13      throw new Error('Enclosure is full');
14    }
15    if (dino.diet === 'carnivore' && this.securityLevel < 8) {
16      throw new Error('Security level too low for carnivores');
17    }
18    this.dinosaurs.push(dino);
19  }
20
21  getDinosaurCount(): number {
22    return this.dinosaurs.length;
23  }
24
25  isEmpty(): boolean {
26    return this.dinosaurs.length === 0;
27  }
28}
29
30// enclosure.test.ts
31describe('Enclosure', () => {
32  let enclosure: Enclosure;
33
34  beforeEach(() => {
35    enclosure = new Enclosure('Zone A', 3, 9);
36  });
37
38  it('should create enclosure with properties', () => {
39    expect(enclosure.name).toBe('Zone A');
40    expect(enclosure.capacity).toBe(3);
41    expect(enclosure.securityLevel).toBe(9);
42  });
43
44  it('should start empty', () => {
45    expect(enclosure.isEmpty()).toBe(true);
46    expect(enclosure.getDinosaurCount()).toBe(0);
47  });
48
49  it('should add dinosaur', () => {
50    const dino = createDinosaur('Rex', 'T-Rex', 'carnivore');
51    enclosure.addDinosaur(dino);
52
53    expect(enclosure.getDinosaurCount()).toBe(1);
54    expect(enclosure.isEmpty()).toBe(false);
55  });
56
57  it('should throw when full', () => {
58    const dinos = [
59      createDinosaur('Rex', 'T-Rex', 'carnivore'),
60      createDinosaur('Blue', 'Velociraptor', 'carnivore'),
61      createDinosaur('Delta', 'Velociraptor', 'carnivore'),
62    ];
63
64    dinos.forEach(d => enclosure.addDinosaur(d));
65
66    const extra = createDinosaur('Echo', 'Velociraptor', 'carnivore');
67    expect(() => enclosure.addDinosaur(extra)).toThrow('Enclosure is full');
68  });
69
70  it('should reject carnivore with low security', () => {
71    const lowSecurity = new Enclosure('Zone B', 5, 5);
72    const rex = createDinosaur('Rex', 'T-Rex', 'carnivore');
73
74    expect(() => lowSecurity.addDinosaur(rex)).toThrow(
75      'Security level too low for carnivores'
76    );
77  });
78});

Type Assertions in Tests

Type assertions let you check that values have the expected shape:

1test('response has correct shape', () => {
2  const response = getApiResponse();
3
4  // Checking object structure
5  expect(response).toEqual(
6    expect.objectContaining({
7      status: expect.any(String),
8      data: expect.any(Array),
9      timestamp: expect.any(Number),
10    })
11  );
12
13  // Checking array elements
14  expect(response.data).toEqual(
15    expect.arrayContaining([
16      expect.objectContaining({
17        id: expect.any(String),
18        name: expect.any(String),
19      })
20    ])
21  );
22});
Go to CodeWorlds