TypeScript dodaje do JavaScript system typów, ale testy nadal sprawdzają zachowanie w runtime. W Parku Jurajskim TypeScript to jak dokumentacja gatunków - mówi nam czego się spodziewać, ale testy weryfikują, czy dinozaury naprawdę się tak zachowują.
Aby testować TypeScript z Jest, potrzebujemy
ts-jest:1npm install --save-dev ts-jest @types/jest1// jest.config.js
2module.exports = {
3 preset: 'ts-jest',
4 testEnvironment: 'node',
5 testMatch: ['**/*.test.ts'],
6};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 sprawdza typy w compile-time
28 // Testy sprawdzają wartości w 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});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 // Oryginalna tablica nie zmieniona
64 expect(repo.getAll()).toHaveLength(1);
65 });
66});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});Asercje typów pozwalają sprawdzić, że wartości mają oczekiwany kształt:
1test('response has correct shape', () => {
2 const response = getApiResponse();
3
4 // Sprawdzenie struktury obiektu
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 // Sprawdzenie elementów tablicy
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});