diff --git a/README.md b/README.md index 63d302ca8..788c48b0a 100644 --- a/README.md +++ b/README.md @@ -1,107 +1,95 @@ -# Basic testing +# ๐Ÿงช Basic Testing Assignment -โš ๏ธ DO NOT SUBMIT PULL REQUESTS TO THIS REPO โš ๏ธ +This repository contains solutions for the [RS School Basic Testing](https://github.com/AlreadyBored/basic-testing) assignment. The goal is to write unit tests using **Jest** for various JavaScript/TypeScript modules that demonstrate different testing techniques. --- -### Prerequisites -1. Install [Node.js](https://nodejs.org/en/download/) -2. Fork this repository: https://github.com/AlreadyBored/basic-testing -3. Clone your newly created repo locally: https://github.com/<%your_github_username%>/basic-testing/ -4. Go to folder `basic-testing` -5. To install all dependencies use [`npm install`](https://docs.npmjs.com/cli/install) -6. Run **test scripts** in command line. -7. You will see the number of skipped, passing and failing tests. +## ๐Ÿ“ฆ Tech Stack ---- - -### Test scripts - -```bash -# run unit tests -$ npm run test - -# with logging -$ npm run test:verbose -``` +- TypeScript +- Jest +- Node.js (v24+ recommended) --- -#### Notes -1. We recommend you to use Node.js of version 24.x.x (24.10.0 or upper) LTS. If you use any of features, that does not supported by Node.js 24, there may be problems with task submit. -2. Please, be sure that each of your tests is limited to 30 sec. -3. Please, be sure you don't have any linter/TS compiler errors. - ---- - -## General task description -Your task is to write unit tests for code, provided in file `index.ts`. - ---- - -### **Simple tests** - -Write unit tests for the `simpleCalculator` function, which performs basic mathematical operations - addition, subtraction, division, multiplication, and exponentiation. Your task is to verify that the operations are executed correctly and that the function returns `null` for invalid input. - -Write your tests in `src/01-simple-tests/index.test.ts`. - ---- - -### **Table tests** - -Your task is to rewrite the tests written in the previous task using the table-driven testing approach, utilizing the appropriate Jest API. - -Write your tests in `src/02-table-tests/index.test.ts`. - ---- +## ๐Ÿš€ Getting Started +### 1. Clone the repository -### **Error handling & async** +```bash +git clone https://github.com/toby-28/basic-testing.git +cd basic-testing +``` -Your task is to test functions that work asynchronously/throw/reject exceptions.. +### 2. Install dependencies -Write your tests in `src/03-error-handling-async/index.test.ts`. +```bash +npm install +``` ---- +### 3. Run tests -### **Testing class** +```bash +npm run test +``` -Your task is to test a class representing a bank account that implements corresponding operations. Please note that some methods of the class invoke others, some operations result in errors, and the implementation is asynchronous and involves the native JS API. These aspects should be taken into account when writing the tests. +For verbose output: -Write your tests in `src/04-test-class/index.test.ts`. +```bash +npm run test:verbose +``` --- -### **Partial mocking** +## ๐Ÿ“ Project Structure -Your task is to utilize the Jest API to partially mock the contents of a module. +``` +basic-testing/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ 01-simple-tests/ +โ”‚ โ”œโ”€โ”€ 02-table-tests/ +โ”‚ โ”œโ”€โ”€ 03-error-handling-async/ +โ”‚ โ”œโ”€โ”€ 04-test-class/ +โ”‚ โ”œโ”€โ”€ 05-partial-mocking/ +โ”‚ โ”œโ”€โ”€ 06-mocking-node-api/ +โ”‚ โ”œโ”€โ”€ 07-mocking-lib-api/ +โ”‚ โ””โ”€โ”€ 08-snapshot-testing/ +โ”œโ”€โ”€ package.json +โ”œโ”€โ”€ tsconfig.json +โ””โ”€โ”€ README.md +``` -Write your tests in `src/05-partial-mocking/index.test.ts`. +Each folder contains: +- `index.ts` โ†’ source code to be tested +- `index.test.ts` โ†’ your test implementation --- -### **Mocking Node.js API** +## โœ… Assignment Requirements -Your task is to test the proper usage of the Node.js API based on commonly used APIs such as the `fs` module, as well as `setTimeout` and `setInterval`. Remember that the tests should not interact with the actual file system and should not rely on real-time! - -Write your tests in `src/06-mocking-node-api/index.test.ts`. +- Write tests for each section using Jest +- Avoid TypeScript or linter errors +- Each test must complete within **30 seconds** +- Do not modify original source files +- Do not submit PRs to this repo โ€” it's for local development only --- -### **Mocking library API** - -Your task is to test that function that utilize library APIs is working correctly (with commonly used libraries such as `axios` and `lodash` as examples). +## ๐Ÿง  Topics Covered -Write your tests in `src/07-mocking-lib-api/index.test.ts`. +| Section | Focus | +|--------|-------| +| 01 | Basic assertions and input validation | +| 02 | Table-driven tests | +| 03 | Error handling and async functions | +| 04 | Testing classes and internal logic | +| 05 | Partial mocking with Jest | +| 06 | Mocking Node.js APIs (`fs`, timers) | +| 07 | Mocking external libraries (`axios`, `lodash`) | +| 08 | Snapshot testing | --- -### **Snapshot testing** - -Your task is to use snapshot testing with Jest and compare it to regular comparison testing. - -Write your tests in `src/08-snapshot-testing/index.test.ts`. - ---- +## ๐Ÿ“œ License -ยฉ [AlreadyBored](https://github.com/AlreadyBored) +This project is for educational use under RS School guidelines. diff --git a/package-lock.json b/package-lock.json index 2bfc1ab15..d0a46ed4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,6 +80,7 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -2388,7 +2389,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -2575,7 +2575,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", "dev": true, - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -2585,8 +2584,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/graceful-fs": { "version": "4.1.9", @@ -2637,8 +2635,7 @@ "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/lodash": { "version": "4.17.16", @@ -2653,6 +2650,7 @@ "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.19.2" } @@ -2718,6 +2716,7 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -2922,7 +2921,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -2932,29 +2930,25 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -2965,15 +2959,13 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -2986,7 +2978,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -2996,7 +2987,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -3005,15 +2995,13 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -3030,7 +3018,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", @@ -3044,7 +3031,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -3057,7 +3043,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -3072,7 +3057,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" @@ -3082,15 +3066,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/acorn": { "version": "8.14.1", @@ -3098,6 +3080,7 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3110,7 +3093,6 @@ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "dev": true, - "peer": true, "peerDependencies": { "acorn": "^8" } @@ -3143,6 +3125,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3159,7 +3142,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, - "peer": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -3451,6 +3433,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -3564,7 +3547,6 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true, - "peer": true, "engines": { "node": ">=6.0" } @@ -3659,8 +3641,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/concat-map": { "version": "0.0.1", @@ -3914,8 +3895,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/escalade": { "version": "3.2.0", @@ -3934,6 +3914,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3990,6 +3971,7 @@ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -4033,7 +4015,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -4199,7 +4180,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "peer": true, "engines": { "node": ">=4.0" } @@ -4218,7 +4198,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, - "peer": true, "engines": { "node": ">=0.8.x" } @@ -4581,8 +4560,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/globals": { "version": "11.12.0", @@ -4934,6 +4912,7 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -5633,7 +5612,6 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, - "peer": true, "engines": { "node": ">=6.11.5" } @@ -5827,8 +5805,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/node-int64": { "version": "0.4.0", @@ -6138,6 +6115,7 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -6257,7 +6235,6 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, - "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -6496,15 +6473,13 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "peer": true + ] }, "node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, - "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -6533,7 +6508,6 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, - "peer": true, "dependencies": { "randombytes": "^2.1.0" } @@ -6759,7 +6733,6 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.1.tgz", "integrity": "sha512-j1n0Ao919h/Ai5r43VAnfV/7azUYW43GPxK7qSATzrsERfW7+y2QW9Cp9ufnRF5CQUWbnLSo7UJokSWCqg4tsQ==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -6778,7 +6751,6 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", @@ -6813,7 +6785,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -6828,7 +6799,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -6844,7 +6814,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -7049,6 +7018,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -7159,6 +7129,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7295,7 +7266,6 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -7357,7 +7327,6 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, - "peer": true, "engines": { "node": ">=10.13.0" } diff --git a/src/01-simple-tests/index.test.ts b/src/01-simple-tests/index.test.ts index fbbea85de..7d8dc665c 100644 --- a/src/01-simple-tests/index.test.ts +++ b/src/01-simple-tests/index.test.ts @@ -1,32 +1,53 @@ -// 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 +describe('simpleCalculator', () => { + test('adds two numbers', () => { + const input = { a: 2, b: 3, action: Action.Add }; + expect(simpleCalculator(input)).toBe(5); }); - test('should subtract two numbers', () => { - // Write your test here + test('subtracts two numbers', () => { + const input = { a: 5, b: 2, action: Action.Subtract }; + expect(simpleCalculator(input)).toBe(3); }); - test('should multiply two numbers', () => { - // Write your test here + test('multiplies two numbers', () => { + const input = { a: 4, b: 3, action: Action.Multiply }; + expect(simpleCalculator(input)).toBe(12); }); - test('should divide two numbers', () => { - // Write your test here + test('divides two numbers', () => { + const input = { a: 10, b: 2, action: Action.Divide }; + expect(simpleCalculator(input)).toBe(5); }); - test('should exponentiate two numbers', () => { - // Write your test here + test('exponentiates two numbers', () => { + const input = { a: 2, b: 3, action: Action.Exponentiate }; + expect(simpleCalculator(input)).toBe(8); }); - test('should return null for invalid action', () => { - // Write your test here + test('returns null for invalid action', () => { + const input = { a: 2, b: 3, action: '%' }; + expect(simpleCalculator(input)).toBeNull(); }); - test('should return null for invalid arguments', () => { - // Write your test here + test('returns null for non-number a', () => { + const input = { a: '2', b: 3, action: Action.Add }; + expect(simpleCalculator(input)).toBeNull(); + }); + + test('returns null for non-number b', () => { + const input = { a: 2, b: '3', action: Action.Add }; + expect(simpleCalculator(input)).toBeNull(); + }); + + test('returns null for missing action', () => { + const input = { a: 2, b: 3, action: undefined }; + expect(simpleCalculator(input)).toBeNull(); + }); + + test('returns null for completely invalid input', () => { + const input = { a: null, b: {}, action: 'invalid' }; + expect(simpleCalculator(input)).toBeNull(); }); }); diff --git a/src/02-table-tests/index.test.ts b/src/02-table-tests/index.test.ts index 4f36e892e..6c20a0bde 100644 --- a/src/02-table-tests/index.test.ts +++ b/src/02-table-tests/index.test.ts @@ -1,17 +1,28 @@ -// 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: 3, action: Action.Subtract, expected: 2 }, + { a: 10, b: 4, action: Action.Subtract, expected: 6 }, + + { a: 2, b: 3, action: Action.Multiply, expected: 6 }, + { a: 4, b: 5, action: Action.Multiply, expected: 20 }, + + { a: 8, b: 2, action: Action.Divide, expected: 4 }, + { a: 9, b: 3, action: Action.Divide, expected: 3 }, + + { a: 2, b: 3, action: Action.Exponentiate, expected: 8 }, + { a: 5, b: 2, action: Action.Exponentiate, expected: 25 }, +]; 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)( + 'calculates $a $action $b = $expected', + ({ 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..b37e138ea 100644 --- a/src/03-error-handling-async/index.test.ts +++ b/src/03-error-handling-async/index.test.ts @@ -1,30 +1,38 @@ -// 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 = { name: 'Hushnudbek' }; + await expect(resolveValue(value)).resolves.toEqual(value); }); }); describe('throwError', () => { test('should throw error with provided message', () => { - // Write your test here + expect(() => throwError('Custom message')).toThrow('Custom message'); }); 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); + expect(() => throwCustomError()).toThrow('This is my awesome custom error!'); }); }); describe('rejectCustomError', () => { test('should reject custom error', async () => { - // Write your test here + await expect(rejectCustomError()).rejects.toThrow(MyAwesomeError); + await expect(rejectCustomError()).rejects.toThrow('This is my awesome custom error!'); }); }); diff --git a/src/04-test-class/index.test.ts b/src/04-test-class/index.test.ts index 937490d82..cd7fec314 100644 --- a/src/04-test-class/index.test.ts +++ b/src/04-test-class/index.test.ts @@ -1,44 +1,72 @@ -// Uncomment the code below and write your tests -// import { getBankAccount } from '.'; +import { + getBankAccount, + InsufficientFundsError, + TransferFailedError, + SynchronizationFailedError, +} from './index'; 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 + const account = getBankAccount(50); + expect(() => account.withdraw(100)).toThrow(InsufficientFundsError); + expect(() => account.withdraw(100)).toThrow('Insufficient funds: cannot withdraw more than 50'); }); test('should throw error when transferring more than balance', () => { - // Write your test here + const acc1 = getBankAccount(30); + const acc2 = getBankAccount(100); + expect(() => acc1.transfer(50, acc2)).toThrow(InsufficientFundsError); }); test('should throw error when transferring to the same account', () => { - // Write your test here + const account = getBankAccount(100); + expect(() => account.transfer(10, account)).toThrow(TransferFailedError); + expect(() => account.transfer(10, account)).toThrow('Transfer failed'); }); test('should deposit money', () => { - // Write your test here + const account = getBankAccount(100); + account.deposit(50); + expect(account.getBalance()).toBe(150); }); test('should withdraw money', () => { - // Write your test here + const account = getBankAccount(100); + account.withdraw(40); + expect(account.getBalance()).toBe(60); }); test('should transfer money', () => { - // Write your test here + const acc1 = getBankAccount(100); + const acc2 = getBankAccount(50); + acc1.transfer(30, acc2); + expect(acc1.getBalance()).toBe(70); + expect(acc2.getBalance()).toBe(80); }); test('fetchBalance should return number in case if request did not failed', async () => { - // Write your tests here + const account = getBankAccount(100); + jest.spyOn(account, 'fetchBalance').mockResolvedValue(42); + 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(100); + jest.spyOn(account, 'fetchBalance').mockResolvedValue(75); + await account.synchronizeBalance(); + expect(account.getBalance()).toBe(75); }); test('should throw SynchronizationFailedError if fetchBalance returned null', async () => { - // Write your tests here + const account = getBankAccount(100); + jest.spyOn(account, 'fetchBalance').mockResolvedValue(null); + await expect(account.synchronizeBalance()).rejects.toThrow(SynchronizationFailedError); + await expect(account.synchronizeBalance()).rejects.toThrow('Synchronization failed'); }); }); diff --git a/src/05-partial-mocking/index.test.ts b/src/05-partial-mocking/index.test.ts index 9d8a66cbd..8db6cf49c 100644 --- a/src/05-partial-mocking/index.test.ts +++ b/src/05-partial-mocking/index.test.ts @@ -1,8 +1,14 @@ -// 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 { + ...originalModule, + mockOne: jest.fn(), + mockTwo: jest.fn(), + mockThree: jest.fn(), + }; }); describe('partial mocking', () => { @@ -11,10 +17,18 @@ describe('partial mocking', () => { }); test('mockOne, mockTwo, mockThree should not log into console', () => { - // Write your test here + const consoleSpy = jest.spyOn(console, 'log'); + mockOne(); + mockTwo(); + mockThree(); + expect(consoleSpy).not.toHaveBeenCalled(); + consoleSpy.mockRestore(); }); test('unmockedFunction should log into console', () => { - // Write your test here + const consoleSpy = jest.spyOn(console, 'log'); + unmockedFunction(); + expect(consoleSpy).toHaveBeenCalledWith('I am not mocked'); + consoleSpy.mockRestore(); }); }); diff --git a/src/06-mocking-node-api/index.test.ts b/src/06-mocking-node-api/index.test.ts index 8dc3afd79..312fad2b6 100644 --- a/src/06-mocking-node-api/index.test.ts +++ b/src/06-mocking-node-api/index.test.ts @@ -1,52 +1,111 @@ -// Uncomment the code below and write your tests -// import { readFileAsynchronously, doStuffByTimeout, doStuffByInterval } from '.'; +import { + readFileAsynchronously, + doStuffByTimeout, + doStuffByInterval, +} from './index'; +import * as path from 'path'; + +jest.mock('fs', () => ({ + existsSync: jest.fn(), +})); + +jest.mock('fs/promises', () => ({ + readFile: jest.fn(), +})); + +import { existsSync } from 'fs'; +import { readFile } from 'fs/promises'; describe('doStuffByTimeout', () => { + let timeoutSpy: jest.SpyInstance; + beforeAll(() => { jest.useFakeTimers(); + timeoutSpy = jest.spyOn(global, 'setTimeout'); }); afterAll(() => { jest.useRealTimers(); + timeoutSpy.mockRestore(); }); test('should set timeout with provided callback and timeout', () => { - // Write your test here + const callback = jest.fn(); + doStuffByTimeout(callback, 1000); + jest.runAllTimers(); // <-- advance timers to trigger setTimeout + expect(callback).toHaveBeenCalled(); }); test('should call callback only after timeout', () => { - // Write your test here + const callback = jest.fn(); + doStuffByTimeout(callback, 1000); + expect(callback).not.toHaveBeenCalled(); + jest.advanceTimersByTime(1000); + expect(callback).toHaveBeenCalledTimes(1); }); }); describe('doStuffByInterval', () => { + let intervalSpy: jest.SpyInstance; + beforeAll(() => { jest.useFakeTimers(); + intervalSpy = jest.spyOn(global, 'setInterval'); }); afterAll(() => { jest.useRealTimers(); + intervalSpy.mockRestore(); }); test('should set interval with provided callback and timeout', () => { - // Write your test here + const callback = jest.fn(); + doStuffByInterval(callback, 500); + jest.advanceTimersByTime(1500); // <-- simulate 3 intervals + expect(callback).toHaveBeenCalledTimes(3); }); test('should call callback multiple times after multiple intervals', () => { - // Write your test here + const callback = jest.fn(); + doStuffByInterval(callback, 500); + jest.advanceTimersByTime(1500); + expect(callback).toHaveBeenCalledTimes(3); }); }); describe('readFileAsynchronously', () => { - test('should call join with pathToFile', async () => { - // Write your test here + const fakePath = 'test.txt'; + const fullPath = path.join(__dirname, fakePath); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return file content if file exists', async () => { + (existsSync as jest.Mock).mockReturnValue(true); + (readFile as jest.Mock).mockResolvedValue( + Buffer.from('Hello, Hushnudbek!'), + ); + + const result = await readFileAsynchronously(fakePath); + expect(result).toBe('Hello, Hushnudbek!'); + expect(readFile).toHaveBeenCalledWith(path.join(__dirname, fakePath)); }); test('should return null if file does not exist', async () => { - // Write your test here + (existsSync as jest.Mock).mockReturnValue(false); + const result = await readFileAsynchronously(fakePath); + expect(result).toBeNull(); + expect(readFile).not.toHaveBeenCalled(); }); test('should return file content if file exists', async () => { - // Write your test here + (existsSync as jest.Mock).mockReturnValue(true); + (readFile as jest.Mock).mockResolvedValue( + Buffer.from('Hello, Hushnudbek!'), + ); + const result = await readFileAsynchronously(fakePath); + expect(result).toBe('Hello, Hushnudbek!'); + expect(readFile).toHaveBeenCalledWith(fullPath); }); }); diff --git a/src/07-mocking-lib-api/index.test.ts b/src/07-mocking-lib-api/index.test.ts index e1dd001ef..d5f692570 100644 --- a/src/07-mocking-lib-api/index.test.ts +++ b/src/07-mocking-lib-api/index.test.ts @@ -1,17 +1,44 @@ -// 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', () => { + let axiosClientMock: { get: jest.Mock }; + + beforeEach(() => { + jest.useFakeTimers(); + axiosClientMock = { + get: jest.fn().mockResolvedValue({ data: 'mocked response' }), + }; + (axios.create as jest.Mock).mockReturnValue(axiosClientMock); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + test('should create instance with provided base url', async () => { - // Write your test here + await throttledGetDataFromApi('/posts/1'); + jest.runAllTimers(); + + expect(axios.create).toHaveBeenCalledWith({ + baseURL: 'https://jsonplaceholder.typicode.com', + }); }); test('should perform request to correct provided url', async () => { - // Write your test here + await throttledGetDataFromApi('/posts/1'); + jest.runAllTimers(); + + expect(axiosClientMock.get).toHaveBeenCalledWith('/posts/1'); }); test('should return response data', async () => { - // Write your test here + const result = await throttledGetDataFromApi('/posts/1'); + jest.runAllTimers(); + + expect(result).toBe('mocked response'); }); }); diff --git a/src/08-snapshot-testing/index.test.ts b/src/08-snapshot-testing/index.test.ts index 67c345706..d91e3b547 100644 --- a/src/08-snapshot-testing/index.test.ts +++ b/src/08-snapshot-testing/index.test.ts @@ -1,14 +1,27 @@ -// 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, 2, 3]); + expect(list).toStrictEqual({ + value: 1, + next: { + value: 2, + next: { + value: 3, + next: { + value: null, + next: null, + }, + }, + }, + }); }); // Check match by comparison with snapshot test('should generate linked list from values 2', () => { - // Write your test here + const list = generateLinkedList(['a', 'b', 'c']); + expect(list).toMatchSnapshot(); }); });