diff --git a/.gitignore b/.gitignore index c6bba5913..0c2e03aba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Logs logs *.log +.idea npm-debug.log* yarn-debug.log* yarn-error.log* diff --git a/src/01-simple-tests/index.test.ts b/src/01-simple-tests/index.test.ts index fbbea85de..f89abd10b 100644 --- a/src/01-simple-tests/index.test.ts +++ b/src/01-simple-tests/index.test.ts @@ -1,32 +1,49 @@ -// Uncomment the code below and write your tests -// import { simpleCalculator, Action } from './index'; +import { simpleCalculator, Action, RawCalculatorInput } from './index'; describe('simpleCalculator tests', () => { + let value: RawCalculatorInput; + + beforeEach(() => { + value = { a: 4, b: 2, action: Action.Add }; + }); + test('should add two numbers', () => { - // Write your test here + expect(simpleCalculator(value)).toEqual(6); }); test('should subtract two numbers', () => { - // Write your test here + value.action = Action.Subtract; + + expect(simpleCalculator(value)).toEqual(2); }); test('should multiply two numbers', () => { - // Write your test here + value.action = Action.Multiply; + + expect(simpleCalculator(value)).toEqual(8); }); test('should divide two numbers', () => { - // Write your test here + value.action = Action.Divide; + + expect(simpleCalculator(value)).toEqual(2); }); test('should exponentiate two numbers', () => { - // Write your test here + value.action = Action.Exponentiate; + + expect(simpleCalculator(value)).toEqual(16); }); test('should return null for invalid action', () => { - // Write your test here + value.action = 'unknown'; + + expect(simpleCalculator(value)).toEqual(null); }); test('should return null for invalid arguments', () => { - // Write your test here + value.a = 'unknown'; + + expect(simpleCalculator(value)).toEqual(null); }); }); diff --git a/src/01-simple-tests/index.ts b/src/01-simple-tests/index.ts index 7068d4a4c..b6609191b 100644 --- a/src/01-simple-tests/index.ts +++ b/src/01-simple-tests/index.ts @@ -6,7 +6,7 @@ export enum Action { Exponentiate = '^', } -type RawCalculatorInput = { +export type RawCalculatorInput = { a: unknown; b: unknown; action: unknown; diff --git a/src/02-table-tests/index.test.ts b/src/02-table-tests/index.test.ts index 4f36e892e..561bfa96c 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'; +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 -]; */ + { + input: { a: 4, b: 2, action: Action.Add }, + expected: 6, + testName: 'add two numbers', + }, + { + input: { a: 4, b: 2, action: Action.Subtract }, + expected: 2, + testName: 'subtract two numbers', + }, + { + input: { a: 4, b: 2, action: Action.Multiply }, + expected: 8, + testName: 'multiply two numbers', + }, + { + input: { a: 4, b: 2, action: Action.Divide }, + expected: 2, + testName: 'divide two numbers', + }, + { + input: { a: 4, b: 2, action: Action.Exponentiate }, + expected: 16, + testName: 'exponential two numbers', + }, + { + input: { a: 4, b: 2, action: 'unknown' }, + expected: null, + testName: 'return null if action invalid', + }, + { + input: { a: 4, b: 'unknown', action: Action.Exponentiate }, + expected: null, + testName: 'return null if value invalid', + }, +]; 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); + it.each(testCases)('should $testName', ({ input, expected }) => { + expect(simpleCalculator(input)).toEqual(expected); }); - // 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..610877e4c 100644 --- a/src/03-error-handling-async/index.test.ts +++ b/src/03-error-handling-async/index.test.ts @@ -1,30 +1,37 @@ -// 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 + await expect(resolveValue(4)).resolves.toBe(4); }); }); describe('throwError', () => { test('should throw error with provided message', () => { - // Write your test here + expect(() => throwError('myError')).toThrow(new Error('myError')); }); test('should throw error with default message if message is not provided', () => { - // Write your test here + expect(() => throwError()).toThrowError('Oops!'); }); }); describe('throwCustomError', () => { test('should throw custom error', () => { - // Write your test here + expect(() => throwCustomError()).toThrow(new MyAwesomeError()); }); }); describe('rejectCustomError', () => { test('should reject custom error', async () => { - // Write your test here + await expect(() => rejectCustomError()).rejects.toThrow( + new MyAwesomeError(), + ); }); }); diff --git a/src/04-test-class/index.test.ts b/src/04-test-class/index.test.ts index 937490d82..9f7e71705 100644 --- a/src/04-test-class/index.test.ts +++ b/src/04-test-class/index.test.ts @@ -1,44 +1,102 @@ // Uncomment the code below and write your tests -// import { getBankAccount } from '.'; +import { + BankAccount, + getBankAccount, + InsufficientFundsError, + TransferFailedError, + SynchronizationFailedError, +} from '.'; + +import lodash from 'lodash'; describe('BankAccount', () => { + const initialFounds = 1000; + let account: BankAccount; + + beforeEach(() => { + account = getBankAccount(initialFounds); + }); + test('should create account with initial balance', () => { - // Write your test here + expect(account).toBeDefined(); + expect(account.getBalance()).toEqual(initialFounds); }); test('should throw InsufficientFundsError error when withdrawing more than balance', () => { - // Write your test here + expect(() => account.withdraw(2000)).toThrow( + new InsufficientFundsError(initialFounds), + ); }); test('should throw error when transferring more than balance', () => { - // Write your test here + const newAccount = new BankAccount(initialFounds); + + expect(() => account.transfer(2000, newAccount)).toThrow( + new InsufficientFundsError(initialFounds), + ); }); test('should throw error when transferring to the same account', () => { - // Write your test here + expect(() => account.transfer(2000, account)).toThrow( + new TransferFailedError(), + ); }); test('should deposit money', () => { - // Write your test here + const deposit = 20; + + expect(account.deposit(deposit).getBalance()).toEqual( + initialFounds + deposit, + ); }); test('should withdraw money', () => { - // Write your test here + const withdraw = 20; + + expect(account.withdraw(withdraw).getBalance()).toEqual( + initialFounds - withdraw, + ); }); test('should transfer money', () => { - // Write your test here + const newAccount = new BankAccount(initialFounds); + const transferredMoney = 20; + + account.transfer(transferredMoney, newAccount); + + expect(account.getBalance()).toEqual(initialFounds - transferredMoney); + expect(newAccount.getBalance()).toEqual(initialFounds + transferredMoney); }); test('fetchBalance should return number in case if request did not failed', async () => { - // Write your tests here + jest.spyOn(lodash, 'random').mockReturnValueOnce(50).mockReturnValueOnce(1); + + await account.fetchBalance().then((result) => { + expect(typeof result).toBe('number'); + }); }); test('should set new balance if fetchBalance returned number', async () => { - // Write your tests here + const currentFounds = 50; + + jest + .spyOn(lodash, 'random') + .mockReturnValueOnce(currentFounds) + .mockReturnValueOnce(1); + + await account.synchronizeBalance(); + + expect(account.getBalance()).toEqual(currentFounds); }); test('should throw SynchronizationFailedError if fetchBalance returned null', async () => { - // Write your tests here + jest + .spyOn(lodash, 'random') + .mockReturnValueOnce(initialFounds) + .mockReturnValueOnce(0); + + await expect(() => account.synchronizeBalance()).rejects.toThrow( + new SynchronizationFailedError(), + ); }); }); diff --git a/src/05-partial-mocking/index.test.ts b/src/05-partial-mocking/index.test.ts index 9d8a66cbd..db151a765 100644 --- a/src/05-partial-mocking/index.test.ts +++ b/src/05-partial-mocking/index.test.ts @@ -1,9 +1,12 @@ // 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'); -}); +jest.mock('./index', () => ({ + ...jest.requireActual('./index'), + mockOne: jest.fn(), + mockTwo: jest.fn(), + mockThree: jest.fn(), +})); describe('partial mocking', () => { afterAll(() => { @@ -11,10 +14,12 @@ describe('partial mocking', () => { }); test('mockOne, mockTwo, mockThree should not log into console', () => { - // Write your test here + mockOne(); + mockTwo(); + mockThree(); }); test('unmockedFunction should log into console', () => { - // Write your test here + unmockedFunction(); }); }); diff --git a/src/06-mocking-node-api/index.test.ts b/src/06-mocking-node-api/index.test.ts index 8dc3afd79..a72dba438 100644 --- a/src/06-mocking-node-api/index.test.ts +++ b/src/06-mocking-node-api/index.test.ts @@ -1,5 +1,13 @@ // Uncomment the code below and write your tests -// import { readFileAsynchronously, doStuffByTimeout, doStuffByInterval } from '.'; +import { readFileAsynchronously, doStuffByTimeout, doStuffByInterval } from '.'; + +import { join } from 'path'; +import { existsSync } from 'fs'; +import { readFile } from 'fs/promises'; + +jest.mock('path'); +jest.mock('fs'); +jest.mock('fs/promises'); describe('doStuffByTimeout', () => { beforeAll(() => { @@ -11,11 +19,26 @@ describe('doStuffByTimeout', () => { }); test('should set timeout with provided callback and timeout', () => { - // Write your test here + jest.spyOn(global, 'setTimeout'); + + const callback = jest.fn(); + const timeout = 100; + doStuffByTimeout(callback, timeout); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenCalledWith(callback, timeout); }); test('should call callback only after timeout', () => { - // Write your test here + const callback = jest.fn(); + const timeout = 100; + doStuffByTimeout(callback, timeout); + + expect(callback).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(200); + + expect(callback).toHaveBeenCalledTimes(1); }); }); @@ -29,24 +52,61 @@ describe('doStuffByInterval', () => { }); test('should set interval with provided callback and timeout', () => { - // Write your test here + jest.spyOn(global, 'setInterval'); + + const callback = jest.fn(); + const interval = 100; + doStuffByInterval(callback, interval); + + expect(setInterval).toHaveBeenCalledTimes(1); + expect(setInterval).toHaveBeenCalledWith(callback, interval); }); test('should call callback multiple times after multiple intervals', () => { - // Write your test here + const callback = jest.fn(); + const interval = 100; + doStuffByInterval(callback, interval); + + expect(callback).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(400); + + expect(callback).toHaveBeenCalledTimes(4); }); }); describe('readFileAsynchronously', () => { + afterAll(() => { + jest.unmock('path'); + jest.unmock('fs'); + }); + test('should call join with pathToFile', async () => { - // Write your test here + const pathToFile = 'pathToFile'; + + await readFileAsynchronously(pathToFile); + + expect(join).toHaveBeenCalledTimes(1); + expect(join).toHaveBeenCalledWith(expect.anything(), pathToFile); }); test('should return null if file does not exist', async () => { - // Write your test here + const pathToFile = 'pathToFile'; + + (existsSync as jest.Mock).mockReturnValueOnce(false); + + await expect(readFileAsynchronously(pathToFile)).resolves.toEqual(null); }); test('should return file content if file exists', async () => { - // Write your test here + const pathToFile = 'pathToFile'; + const fileContent = 'fileContent'; + + (existsSync as jest.Mock).mockReturnValueOnce(true); + (readFile as jest.Mock).mockResolvedValue(fileContent); + + await expect(readFileAsynchronously(pathToFile)).resolves.toEqual( + fileContent, + ); }); }); diff --git a/src/07-mocking-lib-api/index.test.ts b/src/07-mocking-lib-api/index.test.ts index e1dd001ef..08fe64e73 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, { AxiosInstance } from 'axios'; +import { throttledGetDataFromApi } from './index'; + +jest.mock('lodash', () => ({ + throttle: jest.fn((fn) => fn), +})); + +const relativePath = 'relativePath'; +const data = 'data'; +const responseData = { data }; describe('throttledGetDataFromApi', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + test('should create instance with provided base url', async () => { - // Write your test here + const axiosCreateSpy = jest.spyOn(axios, 'create').mockReturnValue({ + get: jest.fn().mockResolvedValue(responseData), + } as unknown as AxiosInstance); + + await throttledGetDataFromApi(relativePath); + + expect(axiosCreateSpy).toHaveBeenCalledWith({ + baseURL: 'https://jsonplaceholder.typicode.com', + }); }); test('should perform request to correct provided url', async () => { - // Write your test here + const getSpied = jest.fn().mockResolvedValue(responseData); + jest + .spyOn(axios, 'create') + .mockReturnValue({ get: getSpied } as unknown as AxiosInstance); + + await throttledGetDataFromApi(relativePath); + + expect(getSpied).toHaveBeenCalledWith(relativePath); }); test('should return response data', async () => { - // Write your test here + jest.spyOn(axios, 'create').mockReturnValue({ + get: jest.fn().mockResolvedValue(responseData), + } as unknown as AxiosInstance); + + await expect(throttledGetDataFromApi(relativePath)).resolves.toEqual(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..775e77a8e --- /dev/null +++ b/src/08-snapshot-testing/__snapshots__/index.test.ts.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generateLinkedList should generate linked list from values 2 1`] = ` +{ + "next": { + "next": { + "next": { + "next": { + "next": null, + "value": null, + }, + "value": [], + }, + "value": 21, + }, + "value": {}, + }, + "value": "abc", +} +`; diff --git a/src/08-snapshot-testing/index.test.ts b/src/08-snapshot-testing/index.test.ts index 67c345706..db1211378 100644 --- a/src/08-snapshot-testing/index.test.ts +++ b/src/08-snapshot-testing/index.test.ts @@ -1,14 +1,32 @@ // Uncomment the code below and write your tests // import { generateLinkedList } from './index'; +import { generateLinkedList } from './index'; + +const linkedList = ['abc', {}, 21, []]; + describe('generateLinkedList', () => { - // Check match by expect(...).toStrictEqual(...) test('should generate linked list from values 1', () => { - // Write your test here + expect(generateLinkedList(linkedList)).toStrictEqual({ + next: { + next: { + next: { + next: { + next: null, + value: null, + }, + value: [], + }, + value: 21, + }, + value: {}, + }, + value: 'abc', + }); }); // Check match by comparison with snapshot test('should generate linked list from values 2', () => { - // Write your test here + expect(generateLinkedList(linkedList)).toMatchSnapshot(); }); });