diff --git a/src/01-simple-tests/index.test.ts b/src/01-simple-tests/index.test.ts index fbbea85de..dee4b7f21 100644 --- a/src/01-simple-tests/index.test.ts +++ b/src/01-simple-tests/index.test.ts @@ -1,32 +1,54 @@ // Uncomment the code below and write your tests -// import { simpleCalculator, Action } from './index'; +import { simpleCalculator, Action } from './index'; describe('simpleCalculator tests', () => { test('should add two numbers', () => { // Write your test here + const result = simpleCalculator({ a: 2, b: 7, action: Action.Add }); + expect(result).toBe(9); }); test('should subtract two numbers', () => { // Write your test here + const result = simpleCalculator({ a: 18, b: 12, action: Action.Subtract }); + expect(result).toBe(6); }); test('should multiply two numbers', () => { // Write your test here + const result = simpleCalculator({ a: 3, b: 8, action: Action.Multiply }); + expect(result).toBe(24); }); test('should divide two numbers', () => { // Write your test here + const result = simpleCalculator({ a: 12, b: 3, action: Action.Divide }); + expect(result).toBe(4); }); test('should exponentiate two numbers', () => { // Write your test here + const result = simpleCalculator({ + a: 2, + b: 3, + action: Action.Exponentiate, + }); + expect(result).toBe(8); }); test('should return null for invalid action', () => { // Write your test here + const result = simpleCalculator({ a: 1, b: 2, action: '%' }); + expect(result).toBeNull(); }); test('should return null for invalid arguments', () => { // Write your test here + const result = simpleCalculator({ + a: '1', + b: 2, + action: Action.Add, + }); + expect(result).toBeNull(); }); }); diff --git a/src/02-table-tests/index.test.ts b/src/02-table-tests/index.test.ts index 4f36e892e..71b7d3b20 100644 --- a/src/02-table-tests/index.test.ts +++ b/src/02-table-tests/index.test.ts @@ -1,17 +1,45 @@ // Uncomment the code below and write your tests -/* import { simpleCalculator, Action } from './index'; - -const testCases = [ - { a: 1, b: 2, action: Action.Add, expected: 3 }, - { a: 2, b: 2, action: Action.Add, expected: 4 }, - { a: 3, b: 2, action: Action.Add, expected: 5 }, - // continue cases for other actions -]; */ +import { simpleCalculator, Action } from './index'; describe('simpleCalculator', () => { - // This test case is just to run this test suite, remove it when you write your own tests - test('should blah-blah', () => { - expect(true).toBe(true); + describe('valid input → correct result', () => { + it.each([ + { a: 1, b: 2, action: Action.Add, expected: 3 }, + { a: 2, b: 2, action: Action.Add, expected: 4 }, + { a: 3, b: 2, action: Action.Add, expected: 5 }, + + { a: 5, b: 3, action: Action.Subtract, expected: 2 }, + { a: -1, b: -1, action: Action.Subtract, expected: 0 }, + + { a: 3, b: 4, action: Action.Multiply, expected: 12 }, + { a: 0, b: 5, action: Action.Multiply, expected: 0 }, + + { a: 10, b: 2, action: Action.Divide, expected: 5 }, + { a: 1, b: 0, action: Action.Divide, expected: Infinity }, + + { a: 2, b: 3, action: Action.Exponentiate, expected: 8 }, + { a: 5, b: 0, action: Action.Exponentiate, expected: 1 }, + ])('$#. $a $action $b ⇒ $expected', ({ a, b, action, expected }) => { + expect(simpleCalculator({ a, b, action })).toBe(expected); + }); + }); + + describe('invalid input → null', () => { + test('unknown action returns null', () => { + const result = simpleCalculator({ + a: 1, + b: 2, + action: '%', // not in Action enum + }); + expect(result).toBeNull(); + }); + + it.each([ + { a: '1', b: 2, action: Action.Add }, + { a: 1, b: '2', action: Action.Subtract }, + { a: '1', b: '2', action: Action.Multiply }, + ])('non-numeric args (%o) return null', (input) => { + expect(simpleCalculator(input)).toBeNull(); + }); }); - // Consider to use Jest table tests API to test all cases above }); diff --git a/src/03-error-handling-async/index.test.ts b/src/03-error-handling-async/index.test.ts index 6e106a6d6..198d0b461 100644 --- a/src/03-error-handling-async/index.test.ts +++ b/src/03-error-handling-async/index.test.ts @@ -1,30 +1,39 @@ // Uncomment the code below and write your tests -// import { throwError, throwCustomError, resolveValue, MyAwesomeError, rejectCustomError } from './index'; +import { + resolveValue, + throwError, + throwCustomError, + rejectCustomError, + MyAwesomeError, +} from './index'; describe('resolveValue', () => { - test('should resolve provided value', async () => { - // Write your test here + test('should resolve the provided value', async () => { + await expect(resolveValue(42)).resolves.toBe(42); + const obj = { a: 1 }; + await expect(resolveValue(obj)).resolves.toBe(obj); }); }); describe('throwError', () => { test('should throw error with provided message', () => { - // Write your test here + const message = 'Boom!'; + expect(() => throwError(message)).toThrow(message); }); - test('should throw error with default message if message is not provided', () => { - // Write your test here + test('should throw error with default message if none provided', () => { + expect(() => throwError()).toThrow('Oops!'); }); }); describe('throwCustomError', () => { - test('should throw custom error', () => { - // Write your test here + test('should throw MyAwesomeError', () => { + expect(() => throwCustomError()).toThrow(MyAwesomeError); }); }); describe('rejectCustomError', () => { - test('should reject custom error', async () => { - // Write your test here + test('should reject with MyAwesomeError', async () => { + await expect(rejectCustomError()).rejects.toBeInstanceOf(MyAwesomeError); }); }); diff --git a/src/04-test-class/index.test.ts b/src/04-test-class/index.test.ts index 937490d82..71424f595 100644 --- a/src/04-test-class/index.test.ts +++ b/src/04-test-class/index.test.ts @@ -1,44 +1,86 @@ // Uncomment the code below and write your tests -// import { getBankAccount } from '.'; +import { + getBankAccount, + InsufficientFundsError, + TransferFailedError, + SynchronizationFailedError, +} from '.'; + +jest.mock('lodash', () => ({ + random: jest.fn(), +})); +import { random } from 'lodash'; +const randomMock = random as jest.MockedFunction; describe('BankAccount', () => { test('should create account with initial balance', () => { - // Write your test here + const account = getBankAccount(100); + expect(account.getBalance()).toBe(100); }); - test('should throw InsufficientFundsError error when withdrawing more than balance', () => { - // Write your test here + test('should deposit money', () => { + const account = getBankAccount(100); + account.deposit(50); + expect(account.getBalance()).toBe(150); }); - test('should throw error when transferring more than balance', () => { - // Write your test here + test('should withdraw money', () => { + const account = getBankAccount(200); + account.withdraw(75); + expect(account.getBalance()).toBe(125); }); - test('should throw error when transferring to the same account', () => { - // Write your test here + test('should transfer money', () => { + const from = getBankAccount(300); + const to = getBankAccount(100); + + from.transfer(120, to); + + expect(from.getBalance()).toBe(180); + expect(to.getBalance()).toBe(220); }); - test('should deposit money', () => { - // Write your test here + test('should throw InsufficientFundsError when withdrawing more than balance', () => { + const account = getBankAccount(50); + expect(() => account.withdraw(60)).toThrow(InsufficientFundsError); }); - test('should withdraw money', () => { - // Write your test here + test('should throw error when transferring more than balance', () => { + const from = getBankAccount(40); + const to = getBankAccount(0); + expect(() => from.transfer(100, to)).toThrow(InsufficientFundsError); }); - test('should transfer money', () => { - // Write your test here + test('should throw error when transferring to the same account', () => { + const account = getBankAccount(100); + expect(() => account.transfer(10, account)).toThrow(TransferFailedError); }); - test('fetchBalance should return number in case if request did not failed', async () => { - // Write your tests here + test('fetchBalance should return number when request does NOT fail', async () => { + randomMock + .mockImplementationOnce(() => 42) // balance + .mockImplementationOnce(() => 1); // requestFailed === false + const account = getBankAccount(0); + + const result = await account.fetchBalance(); + expect(result).toBe(42); }); test('should set new balance if fetchBalance returned number', async () => { - // Write your tests here + const account = getBankAccount(10); + jest.spyOn(account, 'fetchBalance').mockResolvedValue(80); + + await account.synchronizeBalance(); + + expect(account.getBalance()).toBe(80); }); test('should throw SynchronizationFailedError if fetchBalance returned null', async () => { - // Write your tests here + const account = getBankAccount(10); + jest.spyOn(account, 'fetchBalance').mockResolvedValue(null); + + await expect(account.synchronizeBalance()).rejects.toBeInstanceOf( + SynchronizationFailedError, + ); }); }); diff --git a/src/05-partial-mocking/index.test.ts b/src/05-partial-mocking/index.test.ts index 9d8a66cbd..a66baf8aa 100644 --- a/src/05-partial-mocking/index.test.ts +++ b/src/05-partial-mocking/index.test.ts @@ -1,20 +1,52 @@ // Uncomment the code below and write your tests -// import { mockOne, mockTwo, mockThree, unmockedFunction } from './index'; +import type * as ModuleTypes from './index'; jest.mock('./index', () => { - // const originalModule = jest.requireActual('./index'); + const actual = jest.requireActual('./index'); + return { + __esModule: true, + ...actual, + mockOne: jest.fn(), + mockTwo: jest.fn(), + mockThree: jest.fn(), + }; }); +import { mockOne, mockTwo, mockThree, unmockedFunction } from './index'; + describe('partial mocking', () => { + let consoleSpy: jest.SpiedFunction; + + beforeEach(() => { + consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined); + }); + + afterEach(() => { + consoleSpy.mockRestore(); + jest.clearAllMocks(); + }); + afterAll(() => { jest.unmock('./index'); }); - test('mockOne, mockTwo, mockThree should not log into console', () => { - // Write your test here + /* ---------------------------------------------------------------- */ + test('mockOne, mockTwo, mockThree should NOT log to console', () => { + mockOne(); + mockTwo(); + mockThree(); + + expect(consoleSpy).not.toHaveBeenCalled(); + expect(mockOne).toHaveBeenCalledTimes(1); + expect(mockTwo).toHaveBeenCalledTimes(1); + expect(mockThree).toHaveBeenCalledTimes(1); }); - test('unmockedFunction should log into console', () => { - // Write your test here + /* ---------------------------------------------------------------- */ + test('unmockedFunction SHOULD log to console', () => { + unmockedFunction(); + + expect(consoleSpy).toHaveBeenCalledTimes(1); + expect(consoleSpy).toHaveBeenCalledWith('I am not mocked'); }); }); diff --git a/src/06-mocking-node-api/index.test.ts b/src/06-mocking-node-api/index.test.ts index 8dc3afd79..8ce4c9a60 100644 --- a/src/06-mocking-node-api/index.test.ts +++ b/src/06-mocking-node-api/index.test.ts @@ -1,52 +1,110 @@ // Uncomment the code below and write your tests -// import { readFileAsynchronously, doStuffByTimeout, doStuffByInterval } from '.'; +import path from 'path'; +import { existsSync } from 'fs'; +import { readFile as readFilePromise } from 'fs/promises'; -describe('doStuffByTimeout', () => { - beforeAll(() => { - jest.useFakeTimers(); - }); +import { doStuffByTimeout, doStuffByInterval, readFileAsynchronously } from '.'; - afterAll(() => { - jest.useRealTimers(); - }); +jest.mock('fs', () => ({ + existsSync: jest.fn(), +})); + +jest.mock('fs/promises', () => ({ + readFile: jest.fn(), +})); + +const existsSyncMock = existsSync as jest.MockedFunction; +const readFileMock = readFilePromise as jest.MockedFunction< + typeof readFilePromise +>; + +describe('doStuffByTimeout', () => { + beforeAll(() => jest.useFakeTimers()); + afterAll(() => jest.useRealTimers()); + afterEach(() => jest.clearAllMocks()); test('should set timeout with provided callback and timeout', () => { - // Write your test here + const cb = jest.fn(); + const timeout = 5000; + + const spy = jest.spyOn(global, 'setTimeout'); + + doStuffByTimeout(cb, timeout); + + expect(spy).toHaveBeenCalledWith(cb, timeout); }); test('should call callback only after timeout', () => { - // Write your test here + const cb = jest.fn(); + const timeout = 3000; + + doStuffByTimeout(cb, timeout); + + expect(cb).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(timeout); + expect(cb).toHaveBeenCalledTimes(1); }); }); describe('doStuffByInterval', () => { - beforeAll(() => { - jest.useFakeTimers(); - }); + beforeAll(() => jest.useFakeTimers()); + afterAll(() => jest.useRealTimers()); + afterEach(() => jest.clearAllMocks()); - afterAll(() => { - jest.useRealTimers(); - }); + test('should set interval with provided callback and interval', () => { + const cb = jest.fn(); + const interval = 2000; + + const spy = jest.spyOn(global, 'setInterval'); - test('should set interval with provided callback and timeout', () => { - // Write your test here + doStuffByInterval(cb, interval); + + expect(spy).toHaveBeenCalledWith(cb, interval); }); test('should call callback multiple times after multiple intervals', () => { - // Write your test here + const cb = jest.fn(); + const interval = 1000; + + doStuffByInterval(cb, interval); + + jest.advanceTimersByTime(interval * 3); + expect(cb).toHaveBeenCalledTimes(3); }); }); describe('readFileAsynchronously', () => { - test('should call join with pathToFile', async () => { - // Write your test here + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should call path.join with __dirname and pathToFile', async () => { + const joinSpy = jest.spyOn(path, 'join'); + + existsSyncMock.mockReturnValue(false); // avoid real FS access + + await readFileAsynchronously('data.txt'); + + expect(joinSpy).toHaveBeenCalledWith(__dirname, 'data.txt'); }); test('should return null if file does not exist', async () => { - // Write your test here + existsSyncMock.mockReturnValue(false); + + const result = await readFileAsynchronously('missing.txt'); + + expect(result).toBeNull(); + expect(readFileMock).not.toHaveBeenCalled(); }); test('should return file content if file exists', async () => { - // Write your test here + existsSyncMock.mockReturnValue(true); + readFileMock.mockResolvedValue(Buffer.from('Hello, Jest!')); + + const result = await readFileAsynchronously('file.txt'); + + expect(result).toBe('Hello, Jest!'); + expect(readFileMock).toHaveBeenCalled(); }); }); diff --git a/src/07-mocking-lib-api/index.test.ts b/src/07-mocking-lib-api/index.test.ts index e1dd001ef..b5eef4136 100644 --- a/src/07-mocking-lib-api/index.test.ts +++ b/src/07-mocking-lib-api/index.test.ts @@ -1,17 +1,50 @@ // Uncomment the code below and write your tests -/* import axios from 'axios'; -import { throttledGetDataFromApi } from './index'; */ +import axios, { AxiosInstance } from 'axios'; +import { throttledGetDataFromApi } from './index'; + +jest.mock('lodash', () => ({ + throttle: (fn: unknown) => fn, +})); + +jest.mock('axios'); +const mockedAxios = axios as jest.Mocked; describe('throttledGetDataFromApi', () => { - test('should create instance with provided base url', async () => { - // Write your test here + afterEach(() => { + jest.clearAllMocks(); }); - test('should perform request to correct provided url', async () => { - // Write your test here + const givenAxiosGetReturns = (payload: unknown) => { + const get = jest.fn().mockResolvedValue({ data: payload }); + mockedAxios.create.mockReturnValue({ get } as unknown as AxiosInstance); + return get; + }; + + it('should create instance with provided base url', async () => { + givenAxiosGetReturns({}); + + await throttledGetDataFromApi('/todos/1'); + + expect(mockedAxios.create).toHaveBeenCalledWith({ + baseURL: 'https://jsonplaceholder.typicode.com', + }); }); - test('should return response data', async () => { - // Write your test here + it('should perform request to correct provided url', async () => { + const path = '/posts/2'; + const getSpy = givenAxiosGetReturns({}); + + await throttledGetDataFromApi(path); + + expect(getSpy).toHaveBeenCalledWith(path); + }); + + it('should return response data', async () => { + const fakeData = { id: 42, title: 'Answer' }; + givenAxiosGetReturns(fakeData); + + const result = await throttledGetDataFromApi('/anything'); + + expect(result).toEqual(fakeData); }); }); diff --git a/src/08-snapshot-testing/index.test.ts b/src/08-snapshot-testing/index.test.ts index 67c345706..0f8730eae 100644 --- a/src/08-snapshot-testing/index.test.ts +++ b/src/08-snapshot-testing/index.test.ts @@ -1,14 +1,30 @@ // Uncomment the code below and write your tests -// import { generateLinkedList } from './index'; +import { generateLinkedList } from './index'; describe('generateLinkedList', () => { - // Check match by expect(...).toStrictEqual(...) test('should generate linked list from values 1', () => { - // Write your test here + const values = [1, 2, 3]; + const expected = { + value: 1, + next: { + value: 2, + next: { + value: 3, + next: { + value: null, + next: null, + }, + }, + }, + }; + + expect(generateLinkedList(values)).toStrictEqual(expected); }); - // Check match by comparison with snapshot test('should generate linked list from values 2', () => { - // Write your test here + const values = ['a', 'b', 'c', 'd']; + const list = generateLinkedList(values); + + expect(list).toMatchSnapshot(); }); });