diff --git a/package.json b/package.json index 7bf6c5ad2..b37f17fa1 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "format": "prettier --write \"src/**/*.ts\"", "lint": "eslint \"src/**/*.ts\" --fix", "test": "jest --silent", - "test:verbose": "jest", + "test:verbose": "jest --verbose --runInBand --detectOpenHandles", + "test:verbose:coverage": "jest --verbose --runInBand --detectOpenHandles --collectCoverage", "build": "tsc" }, "devDependencies": { diff --git a/src/01-simple-tests/index.test.ts b/src/01-simple-tests/index.test.ts index 580981ac2..bfe3a5732 100644 --- a/src/01-simple-tests/index.test.ts +++ b/src/01-simple-tests/index.test.ts @@ -11,9 +11,9 @@ describe('simpleCalculator tests', () => { test('should subtract two numbers', () => { // Write your test here - const [a, b] = [5, 10]; - const result = simpleCalculator({ a: a, b: b, action: Action.Add }); - expect(result).toBe(a + b); + const [a, b] = [10, 5]; + const result = simpleCalculator({ a: a, b: b, action: Action.Subtract }); + expect(result).toBe(a - b); }); test('should multiply two numbers', () => { diff --git a/src/02-table-tests/index.test.ts b/src/02-table-tests/index.test.ts index 4f36e892e..c81f2466f 100644 --- a/src/02-table-tests/index.test.ts +++ b/src/02-table-tests/index.test.ts @@ -1,17 +1,37 @@ // Uncomment the code below and write your tests -/* import { simpleCalculator, Action } from './index'; +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 -]; */ + { 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: 1, action: Action.Subtract, expected: 4 }, + { a: 4, b: 2, action: Action.Subtract, expected: 2 }, + { a: 8, b: 3, action: Action.Subtract, expected: 5 }, + + { a: 2, b: 3, action: Action.Multiply, expected: 6 }, + { a: 3, b: 4, action: Action.Multiply, expected: 12 }, + { a: 5, b: 2, action: Action.Multiply, expected: 10 }, + + { a: 8, b: 2, action: Action.Divide, expected: 4 }, + { a: 10, b: 5, action: Action.Divide, expected: 2 }, + { a: 15, b: 3, action: Action.Divide, expected: 5 }, + + { a: 5, b: 2, action: Action.Exponentiate, expected: 25 }, + { a: 3, b: 3, action: Action.Exponentiate, expected: 27 }, + { a: 2, b: 5, action: Action.Exponentiate, expected: 32 }, + + // Invalid cases + { a: 2, b: 5, action: 'wrong action', expected: null }, + { a: '2', b: 5, action: Action.Exponentiate, expected: null }, + { a: 2, b: '5', action: Action.Exponentiate, expected: null }, + // continue cases for other actions +]; 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); - }); // Consider to use Jest table tests API to test all cases above + test.each(testCases)('$a $action $b', ({ a, b, action, expected }) => { + expect(simpleCalculator({ a, b, action })).toBe(expected); + }); }); diff --git a/src/03-error-handling-async/index.test.ts b/src/03-error-handling-async/index.test.ts index 6e106a6d6..6ccba01e0 100644 --- a/src/03-error-handling-async/index.test.ts +++ b/src/03-error-handling-async/index.test.ts @@ -1,30 +1,51 @@ // Uncomment the code below and write your tests -// import { throwError, throwCustomError, resolveValue, MyAwesomeError, rejectCustomError } from './index'; +import { + throwError, + throwCustomError, + resolveValue, + MyAwesomeError, + rejectCustomError, +} from './index'; describe('resolveValue', () => { test('should resolve provided value', async () => { // Write your test here + expect.assertions(1); + expect(resolveValue(10)).resolves.toBe(10); }); }); describe('throwError', () => { test('should throw error with provided message', () => { // Write your test here + const errMsg = 'Oops, error occured'; + expect(() => { + throwError(errMsg); + }).toThrow(errMsg); }); test('should throw error with default message if message is not provided', () => { // Write your test here + const defaultErrMsg = 'Oops!'; + expect(() => { + throwError(); + }).toThrow(defaultErrMsg); }); }); describe('throwCustomError', () => { test('should throw custom error', () => { // Write your test here + expect(() => { + throwCustomError(); + }).toThrow(MyAwesomeError); }); }); describe('rejectCustomError', () => { test('should reject custom error', async () => { // Write your test here + expect.assertions(1); + await expect(rejectCustomError()).rejects.toThrow(MyAwesomeError); }); }); diff --git a/src/04-test-class/index.test.ts b/src/04-test-class/index.test.ts index 937490d82..8f1dc6929 100644 --- a/src/04-test-class/index.test.ts +++ b/src/04-test-class/index.test.ts @@ -1,44 +1,125 @@ // Uncomment the code below and write your tests -// import { getBankAccount } from '.'; +import { + getBankAccount, + BankAccount, + InsufficientFundsError, + // SynchronizationFailedError, + TransferFailedError, + SynchronizationFailedError, +} from '.'; + +import { random } from 'lodash'; + +jest.mock('lodash', () => ({ + random: jest.fn(), +})); describe('BankAccount', () => { + const initialBalance = 1000; + test('should create account with initial balance', () => { // Write your test here + const account = getBankAccount(initialBalance); + + expect(account).toBeInstanceOf(BankAccount); + expect(account.getBalance()).toBe(initialBalance); }); test('should throw InsufficientFundsError error when withdrawing more than balance', () => { // Write your test here + const account = getBankAccount(initialBalance); + + expect(() => { + account.withdraw(account.getBalance() + 1000); + }).toThrow(InsufficientFundsError); }); test('should throw error when transferring more than balance', () => { // Write your test here + const account = getBankAccount(initialBalance); + const accountToTransfer = getBankAccount(initialBalance); + + expect(() => { + account.transfer(account.getBalance() + 1000, accountToTransfer); + }).toThrow(InsufficientFundsError); }); test('should throw error when transferring to the same account', () => { // Write your test here + const account = getBankAccount(initialBalance); + + expect(() => { + account.transfer(account.getBalance() + 1000, account); + }).toThrow(TransferFailedError); }); test('should deposit money', () => { // Write your test here + const account = getBankAccount(initialBalance); + const deposit = 2000; + account.deposit(deposit); + + expect(account.getBalance()).toBe(initialBalance + deposit); }); test('should withdraw money', () => { // Write your test here + const account = getBankAccount(initialBalance); + const withdrawAmount = 200; + account.withdraw(withdrawAmount); + + expect(account.getBalance()).toBe(initialBalance - withdrawAmount); }); test('should transfer money', () => { // Write your test here + const account = getBankAccount(initialBalance); + const accountToTransfer = getBankAccount(initialBalance); + const transferAmount = 300; + account.transfer(transferAmount, accountToTransfer); + + expect(account.getBalance()).toBe(initialBalance - transferAmount); + expect(accountToTransfer.getBalance()).toBe( + initialBalance + transferAmount, + ); }); test('fetchBalance should return number in case if request did not failed', async () => { // Write your tests here + + const account = getBankAccount(initialBalance); + + (random as jest.Mock) + .mockImplementationOnce(() => 77) + .mockImplementationOnce(() => 1); + + const balance = await account.fetchBalance(); + + expect(typeof balance).toBe('number'); }); - test('should set new balance if fetchBalance returned number', async () => { + test('synchronizeBalance should set new balance if fetchBalance returned number', async () => { // Write your tests here + const account = getBankAccount(initialBalance); + (random as jest.Mock) + .mockImplementationOnce(() => 77) + .mockImplementationOnce(() => 1); + + await account.synchronizeBalance(); + expect(account.getBalance()).not.toBe(initialBalance); }); - test('should throw SynchronizationFailedError if fetchBalance returned null', async () => { + test('synchronizeBalance should throw SynchronizationFailedError if fetchBalance returned null', async () => { // Write your tests here + const account = getBankAccount(initialBalance); + + (random as jest.Mock) + .mockImplementationOnce(() => 77) + .mockImplementationOnce(() => 0); + + expect.assertions(1); + await expect(account.synchronizeBalance()).rejects.toThrow( + SynchronizationFailedError, + ); }); }); diff --git a/src/04-test-class/index.ts b/src/04-test-class/index.ts index a1f6959e8..9d8ffa928 100644 --- a/src/04-test-class/index.ts +++ b/src/04-test-class/index.ts @@ -39,7 +39,6 @@ export class BankAccount { public async fetchBalance(): Promise { const balance = random(0, 100, false); - const requestFailed = random(0, 1, false) === 0; return requestFailed ? null : balance; diff --git a/src/05-partial-mocking/index.test.ts b/src/05-partial-mocking/index.test.ts index 9d8a66cbd..b6cc3df86 100644 --- a/src/05-partial-mocking/index.test.ts +++ b/src/05-partial-mocking/index.test.ts @@ -1,8 +1,16 @@ // Uncomment the code below and write your tests -// import { mockOne, mockTwo, mockThree, unmockedFunction } from './index'; +import { mockOne, mockTwo, mockThree, unmockedFunction } from './index'; jest.mock('./index', () => { - // const originalModule = jest.requireActual('./index'); + const originalModule = + jest.requireActual('./index'); + + return { + mockOne: jest.fn(), + mockTwo: jest.fn(), + mockThree: jest.fn(), + unmockedFunction: originalModule.unmockedFunction, + }; }); describe('partial mocking', () => { @@ -10,11 +18,22 @@ describe('partial mocking', () => { jest.unmock('./index'); }); - test('mockOne, mockTwo, mockThree should not log into console', () => { + test('mockOne, mockTwo, mockTwo should not log into console', () => { // Write your test here + const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + + mockOne(); + mockTwo(); + mockThree(); + + expect(logSpy).not.toHaveBeenCalled(); }); test('unmockedFunction should log into console', () => { // Write your test here + const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + unmockedFunction(); + + expect(logSpy).toHaveBeenCalled(); }); }); diff --git a/src/06-mocking-node-api/index.test.ts b/src/06-mocking-node-api/index.test.ts index 8dc3afd79..6477ea42a 100644 --- a/src/06-mocking-node-api/index.test.ts +++ b/src/06-mocking-node-api/index.test.ts @@ -1,5 +1,8 @@ // Uncomment the code below and write your tests -// import { readFileAsynchronously, doStuffByTimeout, doStuffByInterval } from '.'; +import path from 'path'; +import { readFileAsynchronously, doStuffByTimeout, doStuffByInterval } from '.'; +import { existsSync } from 'fs'; +import { readFile } from 'fs/promises'; describe('doStuffByTimeout', () => { beforeAll(() => { @@ -12,41 +15,99 @@ describe('doStuffByTimeout', () => { test('should set timeout with provided callback and timeout', () => { // Write your test here + const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + const cb = jest.fn(); + const delay = 1000; + + doStuffByTimeout(cb, delay); + + expect(setTimeoutSpy).toHaveBeenCalledWith(cb, delay); }); test('should call callback only after timeout', () => { // Write your test here + const cb = jest.fn(); + const delay = 1000; + + doStuffByTimeout(cb, delay); + + expect(cb).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(delay); + + expect(cb).toHaveBeenCalled(); }); }); describe('doStuffByInterval', () => { beforeAll(() => { jest.useFakeTimers(); + jest.spyOn(global, 'setInterval'); }); afterAll(() => { jest.useRealTimers(); + jest.resetAllMocks(); }); test('should set interval with provided callback and timeout', () => { // Write your test here + jest.spyOn(global, 'setInterval'); + + const cb = jest.fn(); + const interval = 500; + + doStuffByInterval(cb, interval); + + expect(setInterval).toHaveBeenCalledWith(cb, interval); }); test('should call callback multiple times after multiple intervals', () => { // Write your test here + const cb = jest.fn(); + const interval = 500; + doStuffByInterval(cb, interval); + + expect(cb).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(interval * 3); + + expect(cb).toHaveBeenCalledTimes(3); }); }); +jest.mock('fs'); +jest.mock('fs/promises'); + describe('readFileAsynchronously', () => { test('should call join with pathToFile', async () => { // Write your test here + const joinSpy = jest.spyOn(path, 'join'); + const pathToFile = 'test.txt'; + readFileAsynchronously(pathToFile); + + expect(joinSpy).toHaveBeenCalledWith(__dirname, pathToFile); }); test('should return null if file does not exist', async () => { // Write your test here + (existsSync as jest.Mock).mockReturnValue(false); + + const pathToFile = 'test.txt'; + + expect(await readFileAsynchronously(pathToFile)).toBe(null); }); test('should return file content if file exists', async () => { // Write your test here + const pathToFile = 'test.txt'; + const fileContent = 'file content'; + + (existsSync as jest.Mock).mockReturnValue(true); + (readFile as jest.Mock).mockResolvedValue(fileContent); + + const result = await readFileAsynchronously(pathToFile); + + expect(result).toBe(fileContent); }); }); diff --git a/src/07-mocking-lib-api/index.test.ts b/src/07-mocking-lib-api/index.test.ts index e1dd001ef..842f3b21f 100644 --- a/src/07-mocking-lib-api/index.test.ts +++ b/src/07-mocking-lib-api/index.test.ts @@ -1,17 +1,47 @@ // Uncomment the code below and write your tests -/* import axios from 'axios'; -import { throttledGetDataFromApi } from './index'; */ +import axios from 'axios'; +import { throttledGetDataFromApi } from './index'; + +jest.mock('axios'); describe('throttledGetDataFromApi', () => { + const response = { data: { posts: [] } }; + const url = '/posts'; + let getMock: jest.Mock; + beforeEach(() => { + getMock = jest.fn().mockResolvedValue(response); + (axios.create as jest.Mock).mockReturnValue({ + get: getMock, + }); + }); + + afterEach(() => { + throttledGetDataFromApi.cancel(); // Clean up throttled timeouts + jest.clearAllMocks(); + }); + test('should create instance with provided base url', async () => { // Write your test here + const baseURL = 'https://jsonplaceholder.typicode.com'; + + await throttledGetDataFromApi(url); + + expect(axios.create).toHaveBeenCalledWith({ + baseURL: baseURL, + }); }); test('should perform request to correct provided url', async () => { // Write your test here + await throttledGetDataFromApi(url); + + expect(getMock).toHaveBeenCalledWith(url); }); test('should return response data', async () => { // Write your test here + const response = await throttledGetDataFromApi(url); + + expect(response).toEqual(response); }); }); diff --git a/src/07-mocking-lib-api/index.ts b/src/07-mocking-lib-api/index.ts index 55c9755cc..c643cd930 100644 --- a/src/07-mocking-lib-api/index.ts +++ b/src/07-mocking-lib-api/index.ts @@ -7,8 +7,8 @@ const getDataFromApi = async (relativePath: string) => { const axiosClient = axios.create({ baseURL: 'https://jsonplaceholder.typicode.com', }); - const response = await axiosClient.get(relativePath); + return response.data; }; diff --git a/src/08-snapshot-testing/__snapshots__/index.test.ts.snap b/src/08-snapshot-testing/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000..b00c929a9 --- /dev/null +++ b/src/08-snapshot-testing/__snapshots__/index.test.ts.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generateLinkedList should generate linked list from values 2 1`] = ` +{ + "next": { + "next": { + "next": null, + "value": null, + }, + "value": 2, + }, + "value": 1, +} +`; diff --git a/src/08-snapshot-testing/index.test.ts b/src/08-snapshot-testing/index.test.ts index 67c345706..d01f83ae1 100644 --- a/src/08-snapshot-testing/index.test.ts +++ b/src/08-snapshot-testing/index.test.ts @@ -1,14 +1,22 @@ // 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 list = generateLinkedList([1]); + + const expectedList = { value: 1, next: { value: null, next: null } }; + + expect(list).toStrictEqual(expectedList); }); // Check match by comparison with snapshot test('should generate linked list from values 2', () => { // Write your test here + const list = generateLinkedList([1, 2]); + + expect(list).toMatchSnapshot(); }); });