diff --git a/.eslintrc.js b/.eslintrc.js index 55cf7d9f5..f4f5acf96 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,7 @@ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', - tsconfigRootDir : __dirname, + tsconfigRootDir : __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], @@ -20,5 +20,6 @@ module.exports = { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-unused-vars': 'off' // because it does not work with jest.beforeEach() and shows warnings anyway }, }; diff --git a/.gitignore b/.gitignore index c6bba5913..92147b947 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,5 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +.idea/ diff --git a/babel.config.js b/babel.config.js index f98cbe437..8165fe455 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,6 +1,6 @@ module.exports = { - presets: [ - ['@babel/preset-env', { targets: { node: 'current' } }], - '@babel/preset-typescript', - ], - }; \ No newline at end of file + presets: [ + ['@babel/preset-env', { targets: { node: 'current' } }], + '@babel/preset-typescript', + ], +}; diff --git a/src/01-simple-tests/index.test.ts b/src/01-simple-tests/index.test.ts index fbbea85de..4217ca5ac 100644 --- a/src/01-simple-tests/index.test.ts +++ b/src/01-simple-tests/index.test.ts @@ -1,32 +1,48 @@ // Uncomment the code below and write your tests // import { simpleCalculator, Action } from './index'; +import { Action, simpleCalculator } from './index'; + describe('simpleCalculator tests', () => { test('should add two numbers', () => { - // Write your test here + const action = Action.Add; + expect(simpleCalculator({ a: 48, b: -3, action: action })).toEqual(45); }); test('should subtract two numbers', () => { - // Write your test here + const action = Action.Subtract; + expect(simpleCalculator({ a: 48, b: -3, action: action })).toEqual(51); }); test('should multiply two numbers', () => { - // Write your test here + const action = Action.Multiply; + expect(simpleCalculator({ a: 48, b: -3, action: action })).toEqual(-144); }); test('should divide two numbers', () => { - // Write your test here + const action = Action.Divide; + expect(simpleCalculator({ a: 48, b: -3, action: action })).toEqual(-16); }); test('should exponentiate two numbers', () => { - // Write your test here + const action = Action.Exponentiate; + expect(simpleCalculator({ a: 48, b: 3, action: action })).toEqual(110592); }); test('should return null for invalid action', () => { - // Write your test here + expect(simpleCalculator({ a: 48, b: 3, action: 'NOT_EXISTING' })).toEqual( + null, + ); }); - test('should return null for invalid arguments', () => { - // Write your test here - }); + test.each([Object.values(Action)])( + 'should return null for invalid arguments', + (action) => { + expect(simpleCalculator({ a: '5', b: '6', action: action })).toEqual( + null, + ); + expect(simpleCalculator({ a: 2, b: '7', action: action })).toEqual(null); + expect(simpleCalculator({ a: 'a', b: 5, action: action })).toEqual(null); + }, + ); }); diff --git a/src/02-table-tests/index.test.ts b/src/02-table-tests/index.test.ts index 4f36e892e..2a38168cc 100644 --- a/src/02-table-tests/index.test.ts +++ b/src/02-table-tests/index.test.ts @@ -1,17 +1,42 @@ -// 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: 3, b: 2, action: Action.Subtract, expected: 1 }, + { a: 0, b: 2, action: Action.Subtract, expected: -2 }, + { a: 3, b: 2, action: Action.Multiply, expected: 6 }, + { a: 3, b: 3, action: Action.Multiply, expected: 9 }, + { a: 3, b: -2, action: Action.Multiply, expected: -6 }, + { a: 3, b: -2, action: Action.Divide, expected: -1.5 }, + { a: 0, b: 3, action: Action.Divide, expected: 0 }, + { a: 10000, b: -20, action: Action.Divide, expected: -500 }, + { a: 10, b: 2, action: Action.Exponentiate, expected: 100 }, + { a: 10, b: 0, action: Action.Exponentiate, expected: 1 }, + { a: 1, b: '2', action: Action.Add, expected: null }, + { a: '2', b: 2, action: Action.Add, expected: null }, + { a: 3, b: '2', action: Action.Add, expected: null }, + { a: 3, b: undefined, action: Action.Subtract, expected: null }, + { a: 0, b: {}, action: Action.Subtract, expected: null }, + { a: 3, b: null, action: Action.Multiply, expected: null }, + { a: 3, b: '3', action: Action.Multiply, expected: null }, + { a: 3, b: '-2', action: Action.Multiply, expected: null }, + { a: 3, b: {}, action: Action.Divide, expected: null }, + { a: 0, b: [3], action: Action.Divide, expected: null }, + { a: 10000, b: [-20], action: Action.Divide, expected: null }, + { a: 10, b: [2], action: Action.Exponentiate, expected: null }, + { a: [10], b: 0, action: Action.Exponentiate, expected: null }, +]; 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); + test.each(testCases)('should count result properly', (testCase) => { + expect( + simpleCalculator({ + a: testCase.a, + b: testCase.b, + action: testCase.action, + }), + ).toBe(testCase.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..c5dacf4bd 100644 --- a/src/03-error-handling-async/index.test.ts +++ b/src/03-error-handling-async/index.test.ts @@ -1,30 +1,46 @@ -// 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 + const value = 42; + const result = await resolveValue(value); + expect(result).toBe(value); }); }); describe('throwError', () => { test('should throw error with provided message', () => { - // Write your test here + const messageText = 'Message'; + expect(() => { + throwError(messageText); + }).toThrow(messageText); }); test('should throw error with default message if message is not provided', () => { - // Write your test here + expect(() => { + throwError(); + }).toThrow('Oops!'); }); }); 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 + await expect(async () => { + await rejectCustomError(); + }).rejects.toThrow(MyAwesomeError); }); }); diff --git a/src/04-test-class/index.test.ts b/src/04-test-class/index.test.ts index 937490d82..361dc0137 100644 --- a/src/04-test-class/index.test.ts +++ b/src/04-test-class/index.test.ts @@ -1,44 +1,95 @@ // Uncomment the code below and write your tests // import { getBankAccount } from '.'; +import { BankAccount, getBankAccount } from './index'; + describe('BankAccount', () => { + let account: BankAccount; + test('should create account with initial balance', () => { - // Write your test here + const initial = 42; + account = getBankAccount(initial); + expect(account.getBalance()).toEqual(initial); }); test('should throw InsufficientFundsError error when withdrawing more than balance', () => { - // Write your test here + expect(() => { + account.withdraw(account.getBalance() * 10); + }).toThrow(); }); test('should throw error when transferring more than balance', () => { - // Write your test here + const receiver = getBankAccount(0); + expect(() => { + account.transfer(account.getBalance() * 10, receiver); + }).toThrow(); }); test('should throw error when transferring to the same account', () => { - // Write your test here + expect(() => { + account.transfer(account.getBalance() * 10, account); + }).toThrow(); }); test('should deposit money', () => { - // Write your test here + const currentBalance = account.getBalance(); + const deposit = 10; + account.deposit(deposit); + expect(account.getBalance()).toEqual(currentBalance + deposit); }); test('should withdraw money', () => { - // Write your test here + const currentBalance = account.getBalance(); + const sum = 10; + account.withdraw(sum); + expect(account.getBalance()).toEqual(currentBalance - sum); }); test('should transfer money', () => { - // Write your test here + const receiverInitial = 0; + const receiver = getBankAccount(receiverInitial); + const currentBalance = account.getBalance(); + const sum = 1; + account.transfer(sum, receiver); + expect(account.getBalance()).toEqual(currentBalance - sum); + expect(receiver.getBalance()).toEqual(receiverInitial + sum); }); test('fetchBalance should return number in case if request did not failed', async () => { - // Write your tests here + const res = await account.fetchBalance(); + const typeValid = typeof res === 'object' || typeof res === 'number'; + expect(typeValid).toBeTruthy(); }); test('should set new balance if fetchBalance returned number', async () => { - // Write your tests here + const initial = -42; + const testAccount = getBankAccount(initial); + let errorFlag = false; + try { + await testAccount.synchronizeBalance(); + } catch (error) { + errorFlag = true; + } + + const result = + (errorFlag && testAccount.getBalance() === initial) || + (!errorFlag && testAccount.getBalance() !== initial); + expect(result).toBeTruthy(); }); test('should throw SynchronizationFailedError if fetchBalance returned null', async () => { - // Write your tests here + const initial = -42; + const testAccount = getBankAccount(initial); + let errorFlag = false; + try { + await testAccount.synchronizeBalance(); + } catch (error) { + errorFlag = true; + } + + const result = + (errorFlag && testAccount.getBalance() === initial) || + (!errorFlag && testAccount.getBalance() !== initial); + expect(result).toBeTruthy(); }); }); diff --git a/src/05-partial-mocking/index.test.ts b/src/05-partial-mocking/index.test.ts index 9d8a66cbd..c02272ba1 100644 --- a/src/05-partial-mocking/index.test.ts +++ b/src/05-partial-mocking/index.test.ts @@ -1,8 +1,18 @@ -// Uncomment the code below and write your tests -// import { mockOne, mockTwo, mockThree, unmockedFunction } from './index'; +import { mockOne, mockThree, mockTwo, unmockedFunction } from './index'; + +function nope(): void { + return undefined; +} jest.mock('./index', () => { - // const originalModule = jest.requireActual('./index'); + const originalModule = + jest.requireActual('./index'); + return { + ...originalModule, + mockOne: nope, + mockTwo: nope, + mockThree: nope, + }; }); describe('partial mocking', () => { @@ -11,10 +21,17 @@ describe('partial mocking', () => { }); test('mockOne, mockTwo, mockThree should not log into console', () => { - // Write your test here + const logSpy = jest.spyOn(global.console, 'log'); + mockOne(); + mockTwo(); + mockThree(); + expect(logSpy).toHaveBeenCalledTimes(0); + expect(logSpy.mock.calls).toHaveLength(0); }); test('unmockedFunction should log into console', () => { - // Write your test here + const logSpy = jest.spyOn(global.console, 'log'); + unmockedFunction(); + expect(logSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/src/06-mocking-node-api/index.test.ts b/src/06-mocking-node-api/index.test.ts index 8dc3afd79..a7b99ec46 100644 --- a/src/06-mocking-node-api/index.test.ts +++ b/src/06-mocking-node-api/index.test.ts @@ -1,52 +1,108 @@ -// Uncomment the code below and write your tests -// import { readFileAsynchronously, doStuffByTimeout, doStuffByInterval } from '.'; +import { + readFileAsynchronously, + doStuffByTimeout, + doStuffByInterval, +} from './index'; +import path from 'path'; +import fs from 'fs'; describe('doStuffByTimeout', () => { - beforeAll(() => { - jest.useFakeTimers(); - }); + const mockedTime = 2000; + const nope = (): void => undefined; + + beforeAll(() => jest.useFakeTimers()); + + afterAll(() => jest.useRealTimers()); - afterAll(() => { - jest.useRealTimers(); + let spiedSetTimeout: jest.SpyInstance; + + beforeEach((): void => { + spiedSetTimeout = jest.spyOn(global, 'setTimeout'); }); + afterEach(() => jest.clearAllMocks()); + test('should set timeout with provided callback and timeout', () => { - // Write your test here + doStuffByTimeout(nope, mockedTime); + expect(spiedSetTimeout).toBeCalledWith(nope, mockedTime); }); test('should call callback only after timeout', () => { - // Write your test here + const mockedNope = jest.fn(nope); + + doStuffByTimeout(mockedNope, mockedTime); + + expect(mockedNope).not.toBeCalled(); + + jest.advanceTimersByTime(mockedTime); + expect(mockedNope.mock.calls.length).toBe(1); }); }); describe('doStuffByInterval', () => { - beforeAll(() => { - jest.useFakeTimers(); - }); + const mockedTime = 2000; + const nope = (): void => undefined; + + beforeAll(() => jest.useFakeTimers()); + + afterAll(() => jest.useRealTimers()); + + let spiedInterval: jest.SpyInstance; - afterAll(() => { - jest.useRealTimers(); + beforeEach(() => { + spiedInterval = jest.spyOn(global, 'setInterval'); }); + afterEach(() => jest.clearAllMocks()); + test('should set interval with provided callback and timeout', () => { - // Write your test here + doStuffByInterval(nope, mockedTime); + expect(spiedInterval).toBeCalledWith(nope, mockedTime); }); test('should call callback multiple times after multiple intervals', () => { - // Write your test here + const mockedNope = jest.fn(nope); + + doStuffByInterval(mockedNope, mockedTime); + + expect(mockedNope).not.toBeCalled(); + + jest.advanceTimersByTime(mockedTime); + expect(mockedNope.mock.calls.length).toBe(1); + + jest.advanceTimersByTime(mockedTime); + expect(mockedNope.mock.calls.length).toBe(2); + + jest.advanceTimersByTime(mockedTime); + expect(mockedNope.mock.calls.length).toBe(3); }); }); describe('readFileAsynchronously', () => { + let mockedPathJoin: jest.SpyInstance; + let mockedFsExistsSync: jest.SpyInstance; + const mockedText = '42'; + + beforeEach((): void => { + jest.spyOn(fs.promises, 'readFile').mockResolvedValue(mockedText); + mockedFsExistsSync = jest.spyOn(fs, 'existsSync').mockReturnValue(false); + mockedPathJoin = jest.spyOn(path, 'join'); + }); + + afterEach(() => jest.clearAllMocks()); + test('should call join with pathToFile', async () => { - // Write your test here + await readFileAsynchronously('test-file.txt'); + expect(mockedPathJoin).toBeCalled(); }); test('should return null if file does not exist', async () => { - // Write your test here + await expect(readFileAsynchronously('some-file.txt')).resolves.toBeNull(); }); test('should return file content if file exists', async () => { - // Write your test here + mockedFsExistsSync.mockReturnValueOnce(true); + + await expect(readFileAsynchronously('file.txt')).resolves.toBe(mockedText); }); }); diff --git a/src/07-mocking-lib-api/index.test.ts b/src/07-mocking-lib-api/index.test.ts index e1dd001ef..811340e12 100644 --- a/src/07-mocking-lib-api/index.test.ts +++ b/src/07-mocking-lib-api/index.test.ts @@ -1,17 +1,36 @@ -// Uncomment the code below and write your tests -/* import axios from 'axios'; -import { throttledGetDataFromApi } from './index'; */ +import axios from 'axios'; +import { throttledGetDataFromApi } from './index'; describe('throttledGetDataFromApi', () => { + const templateUrl = 'https://jsonplaceholder.typicode.com'; + const templateSubUrl = '/todos/1'; + const realResponseByThatTemplate = { + userId: 1, + id: 1, + title: 'delectus aut autem', + completed: false, + }; + + beforeAll(() => jest.useFakeTimers()); + afterAll(() => jest.useRealTimers()); + + beforeEach(() => jest.runOnlyPendingTimers()); + afterEach(() => jest.clearAllMocks()); + test('should create instance with provided base url', async () => { - // Write your test here + const mockedAxiosCreate = jest.spyOn(axios, 'create'); + await throttledGetDataFromApi(templateSubUrl); + expect(mockedAxiosCreate).lastCalledWith({ baseURL: templateUrl }); }); test('should perform request to correct provided url', async () => { - // Write your test here + const mockedAxiosGet = jest.spyOn(axios.Axios.prototype, 'get'); + await throttledGetDataFromApi(templateSubUrl); + expect(mockedAxiosGet).lastCalledWith(templateSubUrl); }); test('should return response data', async () => { - // Write your test here + const data = await throttledGetDataFromApi(templateSubUrl); + expect(data).toStrictEqual(realResponseByThatTemplate); }); }); 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..d56db99f9 --- /dev/null +++ b/src/08-snapshot-testing/__snapshots__/index.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generateLinkedList should generate linked list from values 2 1`] = ` +{ + "next": { + "next": { + "next": { + "next": null, + "value": null, + }, + "value": 1, + }, + "value": 2, + }, + "value": 3, +} +`; diff --git a/src/08-snapshot-testing/index.test.ts b/src/08-snapshot-testing/index.test.ts index 67c345706..a90ef66bc 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'; describe('generateLinkedList', () => { + const source1 = [1, 2, 3]; + const source2 = [3, 2, 1]; + + const expectedListFromSource1 = { + next: { + next: { + next: { + next: null, + value: null, + }, + value: 3, + }, + value: 2, + }, + value: 1, + }; + // Check match by expect(...).toStrictEqual(...) test('should generate linked list from values 1', () => { - // Write your test here + const list = generateLinkedList(source1); + expect(list).toStrictEqual(expectedListFromSource1); }); // Check match by comparison with snapshot test('should generate linked list from values 2', () => { - // Write your test here + const list = generateLinkedList(source2); + expect(list).toMatchSnapshot(); }); }); diff --git a/tsconfig.json b/tsconfig.json index 75e87e7f9..48f0ea20e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "moduleResolution": "Node", "noUncheckedIndexedAccess": true, "strictNullChecks": true, - "noUnusedLocals": true, + "noUnusedLocals": false, "noUnusedParameters": true, "outDir": "dist", "removeComments": true,