diff --git a/README.md b/README.md index 7be932e..2e0727a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # arrange-act-assert -Act-Arrange-Assert oriented testing tool. -Read the **UPDATED DOCUMENTATION** in GitHub: https://github.com/Llorx/arrange-act-assert +Zero-dependency lightweight Act-Arrange-Assert oriented testing tool. # Motivation -Focusing lately in unitary testing, I noticed that I wanted to reduce the amount of *brain cycles* that I waste designing and reading tests, so I started adding `// Act // Arrange // Assert` [comments to all my tests](https://github.com/goldbergyoni/javascript-testing-best-practices?tab=readme-ov-file#section-0%EF%B8%8F%E2%83%A3-the-golden-rule) so it helps me to notice when something is not in the proper section and also helps identifying each section on first sight, but there's a thing I love more than testing: design-oriented development. Humans are fallible so I prefer for the tool or project premise to force me to follow methodologies and good practices instead of me applying my own rules over my workflow. The more good practices you are forced to do, the less chances to have a problem because, for example, you had a headache one day and you didn't notice a mistake. +Focusing lately in unitary testing, I noticed that I wanted to reduce the amount of *brain cycles* that I waste designing and reading tests, so I started adding `// Act // Arrange // Assert` [comments to all my tests](https://github.com/goldbergyoni/javascript-testing-best-practices?tab=readme-ov-file#section-0%EF%B8%8F%E2%83%A3-the-golden-rule) so it helps me to notice when something is not in the proper section and also helps identifying each section on first sight, but there's a thing I love more than testing: **design-oriented development**. Humans are fallible so I prefer for the tool or project premise to force me to follow methodologies and good practices instead of me applying my own rules over my workflow. The more good practices you are forced to do, the less chances to have a problem because, for example, you had a headache one day and you didn't notice a mistake. With this idea, I created the Act-Arrange-Assert testing tool that reduces the amount of *brain cycles* wasted when you have to read and design your tests. @@ -59,8 +58,10 @@ test("should do that thing properly", () => { const factory = new MyFactory(); test.after(() => factory.dispose()); const processor = factory.getProcessor(); + // Act const data = processor.processBase(base); + // Assert Assert.deepScriptEqual(data, { a: 2, @@ -72,7 +73,7 @@ This helps to differenciate the sections, for example helping you to avoid mixin ```typescript Assert.deepScriptEqual(processor.processBase(base), {...}); // Bad ``` -Still I don't like the idea of just using comments, because that's a rule I've set to myself. The tool itself stills allows me to do weird things that maybe some day I do for whatever reason. +Still I don't like the idea of just using comments, because that's a rule I've set to myself. The native NodeJS test runner stills allows me to do *weird* things (like multiple acts in a single test) that maybe some day I do for whatever reason. With `arrange-act-assert` it helps design a test like this: ```typescript @@ -108,11 +109,11 @@ test("should do that thing properly", { } }); ``` -If you actually read the code, I bet that one of the first things that you saw were the uppercase sections. I can hear you screaming "ugh those uppercase section names!" and that's precisely my pitch: they're noticeable, they're easy to see, THEY'RE UPPERCASE, so you wasted almost no *brain cycles* identifying them. +If you actually read the code, I bet that one of the first things that you saw were the uppercase sections. I can hear you screaming "ugh those uppercase section names!" and that's precisely **my pitch**: they're noticeable, they're easy to see, THEY'RE UPPERCASE, so you wasted almost no *brain cycles* identifying them. -The tool, by design, helped you to differenciate the method that you are trying to test (the `processBase()` inside the ACT) and what result it should return (the `{ a: 2, b: 27 }` inside the ASSERT). +The tool, by design, helped you to differenciate the method that you are trying to test (the `processBase()` inside the *ACT*) and what result it should return (the `{ a: 2, b: 27 }` inside the *ASSERT*). -Apart from that, the `after` callback has a different approach. It wraps the item to be cleared and returns it in the callback function. This way the item to be cleared is directly linked to the callback that will clear it. +Apart from that, the `after` callback has a different approach. It wraps the item to be cleared and returns it in the callback function. This way the item to be cleared is directly linked to the callback that will clear it, helping you to create individual clearing callbacks for each element that needs to be cleared. Imagine that one clear callback with 3 elements inside fails on the element 2 for whatever reason. The third element is never going to clear and you will end up with a leaking resource that may pollute your remaining tests (*insert panik.png here*). And that's very much it. @@ -128,15 +129,15 @@ test("myTest", { }, ACT?({ myArrange }, after) { // Optional ACT method - // Receives what ARRANGE returns as the first argument + // Receives the ARRANGE return as the first argument // Receives an "after" callback as the second argument const myAct = myArrange + 1; return { myAct }; }, ASSERT?({ myAct }, { myArrange }, after) { // Optional ASSERT method - // Receives what ACT returns as the first argument - // Receives what ARRANGE returns as the second argument + // Receives the ACT return as the first argument + // Receives the ARRANGE return as the second argument // Receives an "after" callback as the third argument myAct === 101; myArrange === 100; @@ -146,15 +147,15 @@ test("myTest", { // check multiple results for the same action, to // avoid having a single ASSERT section with multiple assertions "should assert one thing"({ myAct }, { myArrange }, after) { - // Receives what ACT returns as the first argument - // Receives what ARRANGE returns as the second argument + // Receives the ACT return as the first argument + // Receives the ARRANGE return as the second argument // Receives an "after" callback as the third argument myAct === 101; myArrange === 100; }, "should assert another thing"({ myAct }, { myArrange }, after) { - // Receives what ACT returns as the first argument - // Receives what ARRANGE returns as the second argument + // Receives the ACT return as the first argument + // Receives the ARRANGE return as the second argument // Receives an "after" callback as the third argument myAct === 101; myArrange === 100; @@ -162,9 +163,9 @@ test("myTest", { } }); ``` -All three methods are optional, because you don't need to arrange anything, or maybe you only want to test that the ACT doesn't throw an error. +All three methods are optional, because maybe you don't need to ARRANGE anything, or maybe you only want to test that the ACT doesn't throw an error without any extra boilerplate. -You also have a `describe` to group tests: +You also have a `describe` method to group tests: ```typescript test.describe("myDescribe", (test) => { // The describe callback will receive a new `test` object that @@ -176,14 +177,14 @@ test.describe("myDescribe", (test) => { And you can call as much describes as you want inside another describes: ```typescript test.describe("myDescribe", (test) => { - // Use the new "test" function + // Use the new "node:test" function test.describe("subdescribe 1", (test) => { - // Use the new "test" function + // Use the new "node:test" function test("myTest1", {...}); test("myTest2", {...}); }); test.describe("subdescribe 2", (test) => { - // Use the new "test" function + // Use the new "node:test" function test("myTest1", {...}); test("myTest2", {...}); }); @@ -247,12 +248,12 @@ To run the tests you just have to call in the cli: npx aaa [OPTIONS] ``` The `aaa` cli command accepts these options: -· `--folder STRING`: The path of the folder where the test files are located. Defaults to the current folder. -· `--parallel NUMBER`: This tool runs test files in subprocesses (one new node process per test file). It will run these amounts of files in parallel. Set to `0` to run all the test files in the very same process, although is not recommended. Defaults to the amount of cores that the running computer has. -· `--include-files REGEX`: The regex to apply to each full file path found to consider it a test file to run. You can set multiple regexes by setting this option multiple times. Defaults to `(\\|\/|.*(\.|-|_))(test)(\.|(\.|-|\\|\/).*.)(cjs|mjs|js)$`. -· `--exclude-files REGEX`: The regex to apply to each full file path found to exclude it. Defaults to `\/node_modules\/`. -· `--spawn-args-prefix PREFIX`: It will launch the test files with this prefix in the arguments. You can set multiple prefixes by setting this option multiple times. -· `--clear-module-cache`: When you run test files with `parallel` set to `0` (same process), this flag will delete the module cache so when the TestSuite requires a test file, NodeJS will re-require and re-evaluate the file and its dependencies instead of returning the cache, just in case that you need everything clean. +- `--folder STRING`: The path of the folder where the test files are located. Defaults to the current folder. +- `--parallel NUMBER`: This tool runs test files in subprocesses (one new node process per test file). It will run these amounts of files in parallel. Set to `0` to run all the test files in the very same process, although is not recommended. Defaults to the amount of cores that the running computer has. +- `--include-files REGEX`: The regex to apply to each full file path found to consider it a test file to run. You can set multiple regexes by setting this option multiple times. Defaults to `(\\|\/|.*(\.|-|_))(test)(\.|(\.|-|\\|\/).*.)(cjs|mjs|js)$`. +- `--exclude-files REGEX`: The regex to apply to each full file path found to exclude it. Defaults to `\/node_modules\/`. +- `--spawn-args-prefix PREFIX`: It will launch the test files with this prefix in the arguments. You can set multiple prefixes by setting this option multiple times. +- `--clear-module-cache`: When you run test files with `parallel` set to `0` (same process), this flag will delete the module cache so when the TestSuite requires a test file, NodeJS will re-require and re-evaluate the file and its dependencies instead of returning the cache, just in case that you need everything clean. Alternatively, you can import the `TestSuite` and run your tests programatically: ```typescript @@ -334,4 +335,4 @@ test("Should throw an error when invalid arguments in async function", { } }); ``` -They will return a `Monad` object with the properties `ok` and `error` and the methods `should.ok(VALUE)` and `should.error(ERROR)`. The error validation is done using the [NodeJS Assert.throws() error argument](https://nodejs.org/api/assert.html#assertthrowsfn-error-message). \ No newline at end of file +They will return a `Monad` object with the methods `should.ok(VALUE)`, `should.error(ERROR)` and `match({ ok:(value)=>void, error:(error)=>void })`. The error validation is done using the [NodeJS Assert.throws() error argument](https://nodejs.org/api/assert.html#assertthrowsfn-error-message). diff --git a/package-lock.json b/package-lock.json index 358bdaf..f4929ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,37 +1,45 @@ { "name": "arrange-act-assert", - "version": "0.0.4", + "version": "0.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "arrange-act-assert", - "version": "0.0.4", + "version": "0.0.5", "license": "MIT", "bin": { "aaa": "lib/cli.js" }, "devDependencies": { "@types/node": "^22.7.8", + "arrange-act-assert": "^0.0.5", "typescript": "^5.6.3" } }, "node_modules/@types/node": { - "version": "22.8.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.5.tgz", - "integrity": "sha512-5iYk6AMPtsMbkZqCO1UGF9W5L38twq11S2pYWkybGHH2ogPUvXWNlQqJBzuEZWKj/WRH+QTeiv6ySWqJtvIEgA==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, - "license": "MIT", "dependencies": { - "undici-types": "~6.19.8" + "undici-types": "~6.20.0" + } + }, + "node_modules/arrange-act-assert": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/arrange-act-assert/-/arrange-act-assert-0.0.5.tgz", + "integrity": "sha512-pUz65/p8WJhoZcKgzeTfYklHbi8rKn5WP534I5NAVStz6aHVx2YWV11thV3EGeeJxoaSjS2VjnGSA/FSYk/+xA==", + "dev": true, + "bin": { + "aaa": "lib/cli.js" } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -41,11 +49,10 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true } } } diff --git a/package.json b/package.json index 71cedb2..f4c5fc3 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,7 @@ "build": "npm run clean && npx tsc -p tsconfig.build.json", "build-debug": "npm run clean && npx tsc -p tsconfig.json", "build-test": "npm run clean && npx tsc -p tsconfig.test.json", - "js-coverage": "npm run clean && npm run build-test && npm run test-coverage", - "test": "node --test", - "test-coverage": "node --test --experimental-test-coverage --test-coverage-exclude=**/*.test.* --test-coverage-exclude=**/test_folder_mock/**", - "test-watch": "node --test --watch", + "test": "npx aaa", "clean": "node -e \"require('fs').rm('lib', {recursive: true}, O_o => {})\"" }, "author": "Llorx", @@ -38,6 +35,7 @@ "homepage": "https://github.com/Llorx/arrange-act-assert#readme", "devDependencies": { "@types/node": "^22.7.8", + "arrange-act-assert": "^0.0.5", "typescript": "^5.6.3" } } diff --git a/src/TestSuite/MainContext.test.ts b/src/TestSuite/MainContext.test.ts index e0cc73a..61d9ea4 100644 --- a/src/TestSuite/MainContext.test.ts +++ b/src/TestSuite/MainContext.test.ts @@ -1,21 +1,23 @@ -import { test } from "node:test"; -import * as PATH from "node:path"; -import * as Assert from "node:assert"; +import * as PATH from "path"; +import * as Assert from "assert"; -import { MainContext } from "./MainContext"; +import test from "arrange-act-assert"; +import { MainContext } from "./MainContext"; import { mockFiles } from "../test_folder_mock"; -test.describe("MainContext", () => { - test("Should receive all files", async () => { - // Arrange - const context = new MainContext(); - const test_folder_mock = PATH.dirname(mockFiles["index"]); - - // Act - const files = await context.getFiles(test_folder_mock); - - // Assert - Assert.deepStrictEqual(files.sort(), Object.values(mockFiles).sort(), "Not received all files"); +test.describe("MainContext", (test) => { + test("Should receive all files", { + ARRANGE() { + const context = new MainContext(); + const test_folder_mock = PATH.dirname(mockFiles["index"]); + return { context, test_folder_mock }; + }, + async ACT({ context, test_folder_mock }) { + return await context.getFiles(test_folder_mock); + }, + ASSERT(files) { + Assert.deepStrictEqual(files.sort(), Object.values(mockFiles).sort()); + } }); }); \ No newline at end of file diff --git a/src/TestSuite/TestSuite.test.ts b/src/TestSuite/TestSuite.test.ts index a504bff..a001d7e 100644 --- a/src/TestSuite/TestSuite.test.ts +++ b/src/TestSuite/TestSuite.test.ts @@ -1,69 +1,63 @@ -import { test } from "node:test"; -import * as PATH from "node:path"; -import * as Assert from "node:assert"; +import * as PATH from "path"; +import * as Assert from "assert"; -import { TestSuite } from "./TestSuite"; +import { monad, test, TestFunction } from "arrange-act-assert"; +import { TestSuite } from "./TestSuite"; import { mockFiles } from "../test_folder_mock"; -test.describe("TestSuite", () => { - async function run(parallel:number, error = false, invalid = false) { - // Arrange - const suite = new TestSuite({ - parallel: parallel, - include: invalid ? [/mytest/g] : [/mytest-ok/g], - folder: PATH.dirname(mockFiles["index"]), - ...(parallel === 0 ? { clearModuleCache: true } : {}) - }); - - // Act - if (error) { - process.env.ASSERT_NUMBER_1 = "2"; - test.after(() => { - delete process.env.ASSERT_NUMBER_1; +test.describe("TestSuite", (test) => { + test("should validate parallel option", { + ACT() { + return monad(() => new TestSuite({ + parallel: -1 + })); + }, + ASSERT(res) { + res.should.error({ + message: "Invalid parallel option. Must be >= 0" }); } - try { - const result = await suite.run(); - // Assert - Assert.deepStrictEqual(result.files.sort(), [ - mockFiles["file1.mytest-ok"], - mockFiles["file2.mytest-ok"], - ...(invalid ? [mockFiles["file1.mytest-invalid"]] : []) - ].sort(), "not run all files"); - } catch (e) { - if (!error && !invalid) { - throw e; - } - } - } - test("should validate parallel option", async () => { - Assert.throws(() => new TestSuite({ - parallel: -1 - }), { - message: "Invalid parallel option. Must be >= 0" - }); }); - /*test.describe("parallel", () => { - test("should run all test files", async () => { - await run(1); - }); - test("should error a test file", async () => { - await run(1, true); - }); - test("should handle invalid files", async () => { - await run(1, false, true); - }); - });*/ - test.describe("same process", async () => { - test("should run all test files", async () => { - await run(0); - }); - test("should error a test file", async () => { - await run(0, true); - }); - test("should handle invalid files", async () => { - await run(0, false, true); + test.describe("run suite", (test) => { + function run(test:TestFunction, message:string, parallel:number, error = false, invalid = false) { + test(message, { + ARRANGE(after) { + const suite = new TestSuite({ + parallel: parallel, + include: invalid ? [/mytest/g] : [/mytest-ok/g], + folder: PATH.dirname(mockFiles["index"]), + ...(parallel === 0 ? { clearModuleCache: true } : {}) + }); + if (error) { + process.env.ASSERT_NUMBER_1 = "2"; + after(null, () => { + delete process.env.ASSERT_NUMBER_1; + }); + } + return suite; + }, + async ACT(suite) { + return await suite.run() + }, + ASSERT(res) { + Assert.deepStrictEqual(res.files.sort(), [ + mockFiles["file1.mytest-ok"], + mockFiles["file2.mytest-ok"], + ...(invalid ? [mockFiles["file1.mytest-invalid"]] : []) + ].sort()); + } + }); + } + test.describe("parallel", (test) => { + run(test, "should run all test files", 1); + run(test, "should error a test file", 1, true); + run(test, "should handle invalid files", 1, false, true); + }); + test.describe("same process", (test) => { + run(test, "should run all test files", 0); + run(test, "should error a test file", 0, true); + run(test, "should handle invalid files", 0, false, true); }); }); }); \ No newline at end of file diff --git a/src/filterFiles/filterFiles.test.ts b/src/filterFiles/filterFiles.test.ts index de72e45..05422b3 100644 --- a/src/filterFiles/filterFiles.test.ts +++ b/src/filterFiles/filterFiles.test.ts @@ -1,58 +1,64 @@ -import { test } from "node:test"; +import * as Assert from "assert"; -import { filterFiles } from "./filterFiles"; +import test from "arrange-act-assert"; -test.describe("filterFiles", () => { - // Arrange - const mockFiles = [ - "my/file/test.js", - "my/file/my-test.js", - "my/file/my.test.js", - "my/file/test-my.js", - "my/file/test.my.js", - "my/file/tester.js", - "my/file/tast.js", - "my/test/ok1.js", - "my/test/ok2.js" - ]; - test("should list all test files", () => { - // Act - const files = filterFiles(mockFiles, { - include: [/(\\|\/|.*(\.|-|_))(test)(\.|(\.|-|\\|\/).*.)(cjs|mjs|js)$/i], - exclude: [] - }); +import { filterFiles } from "./filterFiles"; - // Assert - const testFiles = new Set([ +test.describe("filterFiles", (test) => { + function getMockFiles() { + return [ "my/file/test.js", "my/file/my-test.js", "my/file/my.test.js", "my/file/test-my.js", "my/file/test.my.js", + "my/file/tester.js", + "my/file/tast.js", "my/test/ok1.js", "my/test/ok2.js" - ]); - for (const file of files) { - if (!testFiles.delete(file)) { - throw new Error(`File ${file} should not be included`); - } - } - if (testFiles.size > 0) { - throw new Error(`Files ${Array.from(testFiles).join(", ")} should be included`); + ]; + } + test("should list all test files", { + ARRANGE() { + return getMockFiles(); + }, + ACT(mockFiles) { + return filterFiles(mockFiles, { + include: [/(\\|\/|.*(\.|-|_))(test)(\.|(\.|-|\\|\/).*.)(cjs|mjs|js)$/i], + exclude: [] + }); + }, + ASSERT(files) { + Assert.deepStrictEqual(files.sort(), [ + "my/file/test.js", + "my/file/my-test.js", + "my/file/my.test.js", + "my/file/test-my.js", + "my/file/test.my.js", + "my/test/ok1.js", + "my/test/ok2.js" + ].sort()); } }); - test("should exclude test files", () => { - // Act - const files = filterFiles(mockFiles, { - include: [/(\\|\/|.*(\.|-|_))(test)(\.|(\.|-|\\|\/).*.)(cjs|mjs|js)$/i], - exclude: [/ok1/i] - }); - - // Assert - for (const file of files) { - if (file.includes("ok1")) { - throw new Error(`File ${file} should not be included`); - } + test("should exclude test files", { + ARRANGE() { + return getMockFiles(); + }, + ACT(mockFiles) { + return filterFiles(mockFiles, { + include: [/(\\|\/|.*(\.|-|_))(test)(\.|(\.|-|\\|\/).*.)(cjs|mjs|js)$/i], + exclude: [/ok1/i] + }); + }, + ASSERT(files) { + Assert.deepStrictEqual(files.sort(), [ + "my/file/test.js", + "my/file/my-test.js", + "my/file/my.test.js", + "my/file/test-my.js", + "my/file/test.my.js", + "my/test/ok2.js" + ].sort()); } }); -}); \ No newline at end of file +}); diff --git a/src/formatters/default.test.ts b/src/formatters/default.test.ts index 5770980..fe253b1 100644 --- a/src/formatters/default.test.ts +++ b/src/formatters/default.test.ts @@ -1,16 +1,17 @@ -import { test } from "node:test"; +import * as Assert from "assert"; + +import test from "arrange-act-assert"; import { DefaultFormatter, STYLE } from "./default"; import { MessageType, TestType } from "."; -test.describe("default formatter", () => { - // Arrange +test.describe("default formatter", (test) => { class LogChecker { logs:string[] = []; log(msg:string) { this.logs.push(msg); } - assert(type:string, check:unknown[][]) { + assert(check:unknown[][]) { const checkLogs = check.map(line => line.join(" ")); const logs = this.logs.map(msg => { return msg.replaceAll(STYLE.BOLD, "") @@ -19,11 +20,7 @@ test.describe("default formatter", () => { .replaceAll(STYLE.RED, "") .replaceAll(STYLE.RESET, ""); }); - if (JSON.stringify(logs) !== JSON.stringify(checkLogs)) { - console.log("Expected:", checkLogs); - console.log("Found:", logs); - throw new Error(`${type} logs are different`); - } + Assert.deepStrictEqual(logs, checkLogs); this.logs.splice(0); } } @@ -69,151 +66,160 @@ test.describe("default formatter", () => { }; return ret; } - test("should not show logs after start", () => { - // Arrange - const checker = newChecker(); - const test1 = checker.addTest(); - - // Act - test1.start(); - - // Assert - checker.assert("After start", []); + test("should not show logs after start", { + ARRANGE() { + const checker = newChecker(); + const test1 = checker.addTest(); + return { checker, test1 }; + }, + ACT({ test1 }) { + test1.start(); + }, + ASSERT(_, { checker }) { + checker.assert([]); + } }); - - test("should show logs after end", () => { - // Arrange - const checker = newChecker(); - const test1 = checker.addTest(); - - // Act - test1.start(); - test1.end(); - - // Assert - checker.assert("After end", [["√", test1.id]]); + test("should show logs after end", { + ARRANGE() { + const checker = newChecker(); + const test1 = checker.addTest(); + return { checker, test1 }; + }, + ACT({ test1 }) { + test1.start(); + test1.end(); + }, + ASSERT(_, { checker, test1 }) { + checker.assert([["√", test1.id]]); + } }); - - test("should show logs after nested started", () => { - // Arrange - const checker = newChecker(); - const test1 = checker.addTest(); - const test1child = test1.addTest(); - - // Act - test1.start(); - test1child.start(); - - // Assert - checker.assert("After end", [["►", test1.id]]); + test("should show logs after nested started", { + ARRANGE() { + const checker = newChecker(); + const test1 = checker.addTest(); + const test1child = test1.addTest(); + return { checker, test1, test1child }; + }, + ACT({ test1, test1child }) { + test1.start(); + test1child.start(); + }, + ASSERT(_, { checker, test1 }) { + checker.assert([["►", test1.id]]); + } }); - - test("should show end logs after nested ended", () => { - // Arrange - const checker = newChecker(); - const test1 = checker.addTest(); - const test1child = test1.addTest(); - - // Act - test1.start(); - test1child.start(); - test1child.end(); - test1.end(); - - // Assert - checker.assert("After end", [ - ["►", test1.id], - [" √", test1child.id], - ["√", test1.id] - ]); + test("should show end logs after nested ended", { + ARRANGE() { + const checker = newChecker(); + const test1 = checker.addTest(); + const test1child = test1.addTest(); + return { checker, test1, test1child }; + }, + ACT({ test1, test1child }) { + test1.start(); + test1child.start(); + test1child.end(); + test1.end(); + }, + ASSERT(_, { checker, test1, test1child }) { + checker.assert([ + ["►", test1.id], + [" √", test1child.id], + ["√", test1.id] + ]); + } }); - - test("should show end logs in starting order", () => { - // Arrange - const checker = newChecker(); - const test1 = checker.addTest(); - const test2 = checker.addTest(); - - // Act - test1.start(); - test2.start(); - test2.end(); - test1.end(); - - // Assert - checker.assert("After end", [ - ["√", test1.id], - ["√", test2.id] - ]); + test("should show end logs in starting order", { + ARRANGE() { + const checker = newChecker(); + const test1 = checker.addTest(); + const test2 = checker.addTest(); + return { checker, test1, test2 }; + }, + ACT({ test1, test2 }) { + test1.start(); + test2.start(); + test2.end(); + test1.end(); + }, + ASSERT(_, { checker, test1, test2 }) { + checker.assert([ + ["√", test1.id], + ["√", test2.id] + ]); + } }); - - test("should show end logs in nested starting order", () => { - // Arrange - const checker = newChecker(); - const test1 = checker.addTest(); - const test1child = test1.addTest(); - const test2 = checker.addTest(); - const test2child1 = test2.addTest(); - const test2child2 = test2.addTest(); - - // Act - test1.start(); - test2.start(); - test2child1.start(); - test2child2.start(); - test2child2.end(); - test2child1.end(); - test2.end(); - test1child.start(); - test1child.end(); - test1.end(); - - // Assert - checker.assert("After end", [ - ['►', test1.id], - [' √', test1child.id], - ['√', test1.id], - ['►', test2.id], - [' √', test2child1.id], - [' √', test2child2.id], - ['√', test2.id] - ]); + test("should show end logs in nested starting order", { + ARRANGE() { + const checker = newChecker(); + const test1 = checker.addTest(); + const test1child = test1.addTest(); + const test2 = checker.addTest(); + const test2child1 = test2.addTest(); + const test2child2 = test2.addTest(); + return { checker, test1, test1child, test2, test2child1, test2child2 }; + }, + ACT({ test1, test1child, test2, test2child1, test2child2 }) { + test1.start(); + test2.start(); + test2child1.start(); + test2child2.start(); + test2child2.end(); + test2child1.end(); + test2.end(); + test1child.start(); + test1child.end(); + test1.end(); + }, + ASSERT(_, { checker, test1, test1child, test2, test2child1, test2child2 }) { + checker.assert([ + ['►', test1.id], + [' √', test1child.id], + ['√', test1.id], + ['►', test2.id], + [' √', test2child1.id], + [' √', test2child2.id], + ['√', test2.id] + ]); + } }); - test("should show all in order after root starts and ends", () => { - // Arrange - const checker = newChecker(); - const testRoot = checker.addTest(); - const test1child1 = testRoot.addTest(); - const test1child1child = test1child1.addTest(); - const subtest1 = test1child1child.addTest(); - const subtest2 = test1child1child.addTest(); - const subtest3 = test1child1child.addTest(); - - // Act - testRoot.start(); - test1child1.start(); - test1child1child.start(); - subtest1.start(); - subtest1.end(); - subtest2.start(); - subtest2.end(); - subtest3.start(); - subtest3.end(); - test1child1child.end(); - test1child1.end(); - testRoot.end(); - - // Assert - checker.assert("After end", [ - ['►', testRoot.id], - [' ►', test1child1.id], - [' ►', test1child1child.id], - [' √', subtest1.id], - [' √', subtest2.id], - [' √', subtest3.id], - [' √', test1child1child.id], - [' √', test1child1.id], - ['√', testRoot.id] - ]); + test("should show all in order after root starts and ends", { + ARRANGE() { + const checker = newChecker(); + const testRoot = checker.addTest(); + const test1child1 = testRoot.addTest(); + const test1child1child = test1child1.addTest(); + const subtest1 = test1child1child.addTest(); + const subtest2 = test1child1child.addTest(); + const subtest3 = test1child1child.addTest(); + return { checker, testRoot, test1child1, test1child1child, subtest1, subtest2, subtest3 }; + }, + ACT({ testRoot, test1child1, test1child1child, subtest1, subtest2, subtest3 }) { + testRoot.start(); + test1child1.start(); + test1child1child.start(); + subtest1.start(); + subtest1.end(); + subtest2.start(); + subtest2.end(); + subtest3.start(); + subtest3.end(); + test1child1child.end(); + test1child1.end(); + testRoot.end(); + }, + ASSERT(_, { checker, testRoot, test1child1, test1child1child, subtest1, subtest2, subtest3 }) { + checker.assert([ + ['►', testRoot.id], + [' ►', test1child1.id], + [' ►', test1child1child.id], + [' √', subtest1.id], + [' √', subtest2.id], + [' √', subtest3.id], + [' √', test1child1child.id], + [' √', test1child1.id], + ['√', testRoot.id] + ]); + } }); }); \ No newline at end of file diff --git a/src/index.test.ts b/src/index.test.ts index 609e987..4d4c0fc 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,19 +1,32 @@ -import { test } from "node:test"; -import * as Assert from "node:assert"; +import * as Assert from "assert"; +import { test } from "arrange-act-assert"; import { TestSuite } from "./TestSuite/TestSuite"; import { monad, asyncMonad } from "./monad/monad"; import * as INDEX from "./index"; -test.describe("Index", () => { - test("Should export all testing methods", async () => { - // Assert - Assert.strictEqual(typeof INDEX.test, "function", "doesn't have test method"); - Assert.strictEqual(typeof INDEX.describe, "function", "doesn't have describe method"); - Assert.strictEqual(INDEX.default, INDEX.test, "doesn't export defaults"); - Assert.strictEqual(INDEX.TestSuite, TestSuite, "doesn't export TestSuite"); - Assert.strictEqual(INDEX.monad, monad, "doesn't export monad"); - Assert.strictEqual(INDEX.asyncMonad, asyncMonad, "doesn't export asyncMonad"); +test.describe("index", () => { + test("Should export all methods and utils", { + ASSERTS: { + "should export test function"() { + Assert.strictEqual(typeof INDEX.test, "function"); + }, + "should export describe function"() { + Assert.strictEqual(typeof INDEX.describe, "function"); + }, + "should export test function as default"() { + Assert.strictEqual(INDEX.default, INDEX.test); + }, + "should export TestSuite"() { + Assert.strictEqual(INDEX.TestSuite, TestSuite); + }, + "should export monad util"() { + Assert.strictEqual(INDEX.monad, monad); + }, + "should export asyncMonad util"() { + Assert.strictEqual(INDEX.asyncMonad, asyncMonad); + } + } }); }); \ No newline at end of file diff --git a/src/monad/monad.test.ts b/src/monad/monad.test.ts index 3dcb5af..96c3d4e 100644 --- a/src/monad/monad.test.ts +++ b/src/monad/monad.test.ts @@ -1,223 +1,280 @@ -import { test } from "node:test"; -import * as Assert from "node:assert"; +import * as Assert from "assert"; -import { monad, asyncMonad } from "./monad"; +import { test } from "arrange-act-assert"; -test.describe("monad", () => { - test.describe("sync", () => { - test("should return object when ok", () => { - // Act - const res = monad(() => 123); +import { monad, asyncMonad } from "./monad"; - // Assert - Assert.deepStrictEqual(res, { - unwrap: res.unwrap, - match: res.match, - should: res.should - }); +test.describe("monad", (test) => { + test.describe("sync", (test) => { + test("should return object when ok", { + ACT() { + return monad(() => 123); + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + unwrap: res.unwrap, + match: res.match, + should: res.should + }); + } }); - test("should return object when error", () => { - // Act - const res = monad(() => { - throw "ok"; - }); - - // Assert - Assert.deepStrictEqual(res, { - unwrap: res.unwrap, - match: res.match, - should: res.should - }); + test("should return object when error", { + ACT() { + return monad(() => { + throw "ok"; + }); + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + unwrap: res.unwrap, + match: res.match, + should: res.should + }); + } }); - test.describe(".should.X", () => { - test("should assert ok", () => { - // Act - const res = monad(() => 123); - - // Assert - Assert.doesNotThrow(() => res.should.ok(123)); + test.describe("should.X", (test) => { + test("should assert ok", { + ACT() { + return monad(() => 123); + }, + ASSERT(res) { + Assert.doesNotThrow(() => res.should.ok(123)); + } }); - test("should assert error", () => { - // Act - const res = monad(() => { - throw new Error("ok"); - }); - - // Assert - Assert.doesNotThrow(() => res.should.error({ - message: "ok" - })); + test("should assert error", { + ACT() { + return monad(() => { + throw new Error("ok") + }); + }, + ASSERT(res) { + Assert.doesNotThrow(() => res.should.error({ + message: "ok" + })); + } }); }); - test.describe(".unwrap", () => { - test("should unwrap ok", () => { - // Arrange - const res = monad(() => 123); - - // Act - const value = res.unwrap(); - - // Assert - Assert.strictEqual(value, 123); + test.describe(".unwrap", (test) => { + test("should unwrap ok", { + ARRANGE() { + return monad(() => 123); + }, + ACT(res) { + return res.unwrap(); + }, + ASSERT(value) { + Assert.strictEqual(value, 123); + } }); - test("should unwrap error", () => { - // Arrange - const res = monad(() => { - throw new Error("ok"); - }); - - // Assert - Assert.throws(() => res.unwrap(), { - message: "ok" - }); + test("should unwrap error", { + ARRANGE() { + return monad(() => { + throw new Error("ok"); + }); + }, + ACT(res) { + return monad(() => res.unwrap()); + }, + ASSERT(value) { + value.should.error({ + message: "ok" + }); + } }); }); - test.describe(".match", () => { - test("should match ok", () => { - // Arrange - const res = monad(() => 123); - - // Act - res.match({ - ok(value) { - // Assert - Assert.strictEqual(value, 123); - }, - error() { - // Assert - throw new Error("Should not error"); - } - }); + test.describe(".match", (test) => { + test("should match ok", { + ARRANGE() { + return monad(() => 123); + }, + ACT(res) { + const ret:{ + ok?:number|null; + error?:unknown; + } = {}; + res.match({ + ok(value) { + ret.ok = value; + }, + error(error) { + ret.error = error; + } + }); + return ret; + }, + ASSERT(ret) { + Assert.deepStrictEqual(ret, { + ok: 123 + }); + } }); - test("should match error", () => { - // Arrange - const res = monad(() => { - throw "ok"; - }); - - // Act - res.match({ - ok() { - // Assert - throw new Error("Should not ok"); - }, - error(error) { - // Assert - Assert.strictEqual(error, "ok"); - } - }); + test("should match error", { + ARRANGE() { + return monad(() => { + throw "ok"; + }); + }, + ACT(res) { + const ret:{ + ok?:number|null; + error?:unknown; + } = {}; + res.match({ + ok(value) { + ret.ok = value; + }, + error(error) { + ret.error = error; + } + }); + return ret; + }, + ASSERT(ret) { + Assert.deepStrictEqual(ret, { + error: "ok" + }); + } }); }); }); - test.describe("async", () => { - test("should return a promise", () => { - // Act - const res = asyncMonad(async () => 123); - - // Assert - Assert.strictEqual(res instanceof Promise, true); + test.describe("async", (test) => { + test("should return a promise", { + ACT() { + const promise = asyncMonad(async () => 123); + return { promise }; + }, + ASSERT({ promise }) { + Assert.strictEqual(promise instanceof Promise, true); + } }); - test("should return object when ok", async () => { - // Act - const res = await asyncMonad(async () => 123); - - // Assert - Assert.deepStrictEqual(res, { - unwrap: res.unwrap, - match: res.match, - should: res.should - }); + test("should return object when ok", { + async ACT() { + return await asyncMonad(async () => 123); + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + unwrap: res.unwrap, + match: res.match, + should: res.should + }); + } }); - test("should return object when error", async () => { - // Act - const res = await asyncMonad(async () => { - throw "ok"; - }); - - // Assert - Assert.deepStrictEqual(res, { - unwrap: res.unwrap, - match: res.match, - should: res.should - }); + test("should return object when error", { + async ACT() { + return await asyncMonad(async () => { + throw "ok"; + }); + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + unwrap: res.unwrap, + match: res.match, + should: res.should + }); + } }); - test.describe(".should.X", () => { - test("should assert ok", async () => { - // Act - const res = await asyncMonad(async () => 123); - - // Assert - Assert.doesNotThrow(() => res.should.ok(123)); + test.describe("should.X", (test) => { + test("should assert ok", { + async ACT() { + return await asyncMonad(async () => 123); + }, + ASSERT(res) { + Assert.doesNotThrow(() => res.should.ok(123)); + } }); - test("should assert error", async () => { - // Act - const res = await asyncMonad(async () => { - throw new Error("ok"); - }); - - // Assert - Assert.doesNotThrow(() => res.should.error({ - message: "ok" - })); + test("should assert error", { + async ACT() { + return await asyncMonad(async () => { + throw new Error("ok") + }); + }, + ASSERT(res) { + Assert.doesNotThrow(() => res.should.error({ + message: "ok" + })); + } }); }); - test.describe(".unwrap", () => { - test("should unwrap ok", async () => { - // Arrange - const res = await asyncMonad(async () => 123); - - // Act - const value = res.unwrap(); - - // Assert - Assert.strictEqual(value, 123); + test.describe(".unwrap", (test) => { + test("should unwrap ok", { + async ARRANGE() { + return await asyncMonad(async () => 123); + }, + ACT(res) { + return res.unwrap(); + }, + ASSERT(value) { + Assert.strictEqual(value, 123); + } }); - test("should unwrap error", async () => { - // Arrange - const res = await asyncMonad(async () => { - throw new Error("ok"); - }); - - // Assert - Assert.throws(() => res.unwrap(), { - message: "ok" - }); + test("should unwrap error", { + async ARRANGE() { + return await asyncMonad(async () => { + throw new Error("ok"); + }); + }, + ACT(res) { + return monad(() => res.unwrap()); + }, + ASSERT(value) { + value.should.error({ + message: "ok" + }); + } }); }); - test.describe(".match", () => { - test("should match ok", async () => { - // Arrange - const res = await asyncMonad(async () => 123); - - // Act - res.match({ - ok(value) { - // Assert - Assert.strictEqual(value, 123); - }, - error() { - // Assert - throw new Error("Should not error"); - } - }); + test.describe(".match", (test) => { + test("should match ok", { + async ARRANGE() { + return await asyncMonad(async () => 123); + }, + ACT(res) { + const ret:{ + ok?:number|null; + error?:unknown; + } = {}; + res.match({ + ok(value) { + ret.ok = value; + }, + error(error) { + ret.error = error; + } + }); + return ret; + }, + ASSERT(ret) { + Assert.deepStrictEqual(ret, { + ok: 123 + }); + } }); - test("should match error", async () => { - // Arrange - const res = await asyncMonad(async () => { - throw "ok"; - }); - - // Act - res.match({ - ok() { - // Assert - throw new Error("Should not ok"); - }, - error(error) { - // Assert - Assert.strictEqual(error, "ok"); - } - }); + test("should match error", { + async ARRANGE() { + return await asyncMonad(async () => { + throw "ok"; + }); + }, + ACT(res) { + const ret:{ + ok?:number|null; + error?:unknown; + } = {}; + res.match({ + ok(value) { + ret.ok = value; + }, + error(error) { + ret.error = error; + } + }); + return ret; + }, + ASSERT(ret) { + Assert.deepStrictEqual(ret, { + error: "ok" + }); + } }); }); }); diff --git a/src/parallelize/parallelize.test.ts b/src/parallelize/parallelize.test.ts index 4a1efb0..b6664b8 100644 --- a/src/parallelize/parallelize.test.ts +++ b/src/parallelize/parallelize.test.ts @@ -1,114 +1,148 @@ -import { test } from "node:test"; -import { setImmediate } from "node:timers/promises"; +// import { test } from "test"; +import { setImmediate } from "timers/promises"; +import * as Assert from "assert"; -import { parallelize } from "./parallelize"; +import test from "arrange-act-assert"; +import { parallelize } from "./parallelize"; -function assertArrayEqual(msg:string, a:T[], b:T[]) { - if (a.length !== b.length) { - console.log("a", a); - console.log("b", b); - throw new Error(`Arrays are not equal: ${msg}`); - } - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) { - console.log("a", a); - console.log("b", b); - throw new Error(`Arrays are not equal: ${msg}`); - } - } -} -test.describe("parallelize", () => { - test("should run all entries", async () => { - // Arrange - const elements = [0, 1, 2, 3, 4]; - function* consumer(elements:number[]) { - while (true) { - const n = elements.shift(); - if (n == null) { - break; +test.describe("parallelize", (test) => { + test("should run all entries", { + ARRANGE() { + const elements = [0, 1, 2, 3, 4]; + function* consumer(elements:number[]) { + while (true) { + const n = elements.shift(); + if (n == null) { + break; + } + yield Promise.resolve(n); } - yield Promise.resolve(n); } + return { elements, consumer }; + }, + async ACT({ elements, consumer }) { + await parallelize(2, consumer(elements)); + }, + ASSERT(_, { elements }) { + Assert.deepStrictEqual(elements, []); } - - // Act - await parallelize(2, consumer(elements)); - assertArrayEqual("generated", elements, []); }); - - test("should run entries in parallel", async () => { - // Arrange - const elements = [0, 1, 2, 3, 4]; - let waiting:(()=>void)[] = []; - function* waiterConsumer(elements:number[]) { - while (true) { - const n = elements.shift(); - if (n == null) { - break; + test.describe("run entries in parallel (parallel: 2)", (test) => { + function getWaiter() { + const elements = [0, 1, 2, 3, 4]; + let waiting:(()=>void)[] = []; + function* waiterConsumer(elements:number[]) { + while (true) { + const n = elements.shift(); + if (n == null) { + break; + } + yield new Promise(resolve => waiting.push(resolve)); } - yield new Promise(resolve => waiting.push(resolve)); } - } - async function runWaiting(assertSize:number) { - if (waiting.length !== assertSize) { - throw new Error(`There should be ${assertSize} waiting tasks`); + async function runWaiting(assertSize:number) { + if (waiting.length !== assertSize) { + throw new Error(`There should be ${assertSize} waiting tasks`); + } + waiting.splice(0).forEach(resolve => resolve()); + await setImmediate(); // Wait for promises to resolve } - waiting.splice(0).forEach(resolve => resolve()); - await setImmediate(); // Wait for promises to resolve + return { elements, waiterConsumer, runWaiting }; } - - // Act - const parallelPromise = parallelize(2, waiterConsumer(elements)); - - // Act-Assert - assertArrayEqual("1st iteration", [2, 3, 4], elements); - await runWaiting(2); - assertArrayEqual("2nd iteration", [4], elements); - await runWaiting(2); - assertArrayEqual("3rd iteration", [], elements); - await runWaiting(1); - - await parallelPromise; + test("one iteration should process 2 elements", { + ARRANGE() { + return getWaiter(); + }, + ACT({ waiterConsumer, elements }) { + parallelize(2, waiterConsumer(elements)); + }, + ASSERT(_, { elements }) { + Assert.deepStrictEqual([2, 3, 4], elements); + } + }); + test("two iterations should process 4 elements", { + ARRANGE() { + return getWaiter(); + }, + async ACT({ waiterConsumer, elements, runWaiting }) { + parallelize(2, waiterConsumer(elements)); + await runWaiting(2); + }, + ASSERT(_, { elements }) { + Assert.deepStrictEqual([4], elements); + } + }); + test("three iterations should process all elements", { + ARRANGE() { + return getWaiter(); + }, + async ACT({ waiterConsumer, elements, runWaiting }) { + parallelize(2, waiterConsumer(elements)); + await runWaiting(2); + await runWaiting(2); + }, + ASSERT(_, { elements }) { + Assert.deepStrictEqual([], elements); + } + }); + test("four iterations iterations should finish the parallelization", { + ARRANGE() { + return getWaiter(); + }, + async ACT({ waiterConsumer, elements, runWaiting }) { + const promise = parallelize(2, waiterConsumer(elements)); + await runWaiting(2); + await runWaiting(2); + await runWaiting(1); + await promise; + return { promise }; + }, + async ASSERT({ promise }) { + await Assert.doesNotReject(promise); + } + }); }); - - test("should return the states of all the promises", async () => { - // Arrange - const elements = [0, -1, 2, -1, 4]; - function* errorConsumer(elements:number[]) { - while (true) { - const n = elements.shift(); - if (n == null) { - break; - } - yield new Promise((resolve, reject) => { - if (n === -1) { - reject(new Error("Invalid number")); - } else { - resolve(n); + test("should return the states of all the promises", { + ARRANGE() { + const elements = [0, -1, 2, -1, 4]; + function* errorConsumer(elements:number[]) { + while (true) { + const n = elements.shift(); + if (n == null) { + break; } - }); - } - } - - // Act - const parallelResult = await parallelize(2, errorConsumer(elements)); - - // Assert - if (parallelResult.length !== 5) { - throw new Error("Parallel should return 5 entries"); - } - for (let i = 0; i < 5; i++) { - const result = parallelResult[i]!; - if (i === 1 || i === 3) { - if (result.status !== "rejected") { - throw new Error(`Parallel result ${i} should be rejected`); + yield new Promise((resolve, reject) => { + if (n === -1) { + reject(new Error("Invalid number")); + } else { + resolve(n); + } + }); } - } else if (result.status !== "fulfilled") { - throw new Error(`Parallel result ${i} should be fulfilled`); - } else if (result.value !== i) { - throw new Error(`Parallel result ${i} should be have value ${i}`); } + return { elements, errorConsumer }; + }, + async ACT({ elements, errorConsumer }) { + return await parallelize(2, errorConsumer(elements)); + }, + ASSERT(parallelResult) { + Assert.deepStrictEqual(parallelResult, [{ + status: "fulfilled", + value: 0 + }, { + status: "rejected", + reason: new Error("Invalid number") + }, { + status: "fulfilled", + value: 2 + }, { + status: "rejected", + reason: new Error("Invalid number") + }, { + status: "fulfilled", + value: 4 + }]); } }); }); \ No newline at end of file diff --git a/src/spawnTestFile/spawnTestFile.mock.test.ts b/src/spawnTestFile/spawnTestFile.mock.test.ts index 01ce692..92138f2 100644 --- a/src/spawnTestFile/spawnTestFile.mock.test.ts +++ b/src/spawnTestFile/spawnTestFile.mock.test.ts @@ -1,4 +1,4 @@ -import { setTimeout } from "node:timers/promises"; +import { setTimeout } from "timers/promises"; import test from ".."; diff --git a/src/spawnTestFile/spawnTestFile.test.ts b/src/spawnTestFile/spawnTestFile.test.ts index 905e19d..b3d03b4 100644 --- a/src/spawnTestFile/spawnTestFile.test.ts +++ b/src/spawnTestFile/spawnTestFile.test.ts @@ -1,27 +1,33 @@ -import { test } from "node:test"; -import * as PATH from "node:path"; -import * as Assert from "node:assert"; +import * as PATH from "path"; + +import { test, asyncMonad } from "arrange-act-assert"; import { spawnTestFile } from "./spawnTestFile"; -test.describe("spawnTestFile", () => { - test("Should spawn new process", async () => { - await spawnTestFile(PATH.join(__dirname, "spawnTestFile.mock.test.js"), {prefix:[]}, () => {}); +test.describe("spawnTestFile", (test) => { + test("Should spawn new process", { + async ACT() { + await spawnTestFile(PATH.join(__dirname, "spawnTestFile.mock.test.js"), {prefix:[]}, () => {}); + } }); - test("Should fail with exit code", async () => { - await Assert.rejects( - spawnTestFile(PATH.join(__dirname, "not_test_file.js"), - {prefix:[]}, - () => {}), - e => e instanceof Error && e.message.includes("Cannot find module") - ); + test("Should fail with exit code", { + ACT() { + return asyncMonad(() => spawnTestFile(PATH.join(__dirname, "not_test_file.js"), {prefix:[]}, () => {})); + }, + ASSERT(res) { + res.should.error({ + message: /Cannot find module/ + }); + } }); - test("Should spawn with a prefix", async () => { - await Assert.rejects( - spawnTestFile(PATH.join(__dirname, "not_test_file.js"), - {prefix:["--aaa-prefix-test"]}, - () => {}), - e => e instanceof Error && e.message.includes("bad option: --aaa-prefix-test") - ); + test("Should spawn with a prefix", { + ACT() { + return asyncMonad(() => spawnTestFile(PATH.join(__dirname, "not_test_file.js"), {prefix:["--aaa-prefix-test"]}, () => {})); + }, + ASSERT(res) { + res.should.error({ + message: /bad option: --aaa-prefix-test/ + }); + } }); }); \ No newline at end of file diff --git a/src/testRunner/functionRunner.test.ts b/src/testRunner/functionRunner.test.ts index e5ffbf5..2d37d89 100644 --- a/src/testRunner/functionRunner.test.ts +++ b/src/testRunner/functionRunner.test.ts @@ -1,36 +1,44 @@ -import { test } from "node:test"; +import * as Assert from "assert"; + +import test from "arrange-act-assert"; import { functionRunner } from "./functionRunner"; -test.describe("functionRunner", () => { - test("Should run valid function", async () => { - const result = await functionRunner("test", ()=>{}, []); - if (!result.run || !result.ok) { - throw new Error("Function should run"); +test.describe("functionRunner", (test) => { + test("Should run valid function", { + async ACT() { + return await functionRunner("test", () => 123, []); + }, + ASSERT(result) { + Assert.deepStrictEqual(result, { + run: true, + ok: true, + data: 123 + }); } }); - test("Should get invalid function error", async () => { - const result = await functionRunner("test", ()=>{ throw "ok" }, []); - if (!result.run || result.ok) { - throw new Error("Function should run with an error"); - } - if (!result.run || result.error !== "ok") { - throw result.error; - } - }); - test("Should get value from result", async () => { - const result = await functionRunner("test", ()=>{ return "ok" }, []); - if (!result.run || !result.ok) { - throw new Error("Function should run with an error"); - } - if (result.data !== "ok") { - throw new Error("Function should return ok"); + test("Should get invalid function error", { + async ACT() { + return await functionRunner("test", () => { throw "ok" }, []); + }, + ASSERT(result) { + Assert.deepStrictEqual(result, { + run: true, + ok: false, + error: "ok", + type: "test" + }); } }); - test("Should not run an undefined argument", async () => { - const result = await functionRunner("test", undefined, []); - if (result.run) { - throw new Error("Function should not "); + test("Should not run an undefined argument", { + async ACT() { + return await functionRunner("test", undefined, []); + }, + ASSERT(result) { + Assert.deepStrictEqual(result, { + run: false, + data: undefined + }); } }); }); \ No newline at end of file diff --git a/src/testRunner/testRunner.test.ts b/src/testRunner/testRunner.test.ts index f577df8..18fb25d 100644 --- a/src/testRunner/testRunner.test.ts +++ b/src/testRunner/testRunner.test.ts @@ -1,6 +1,8 @@ -import { test } from "node:test"; -import * as Assert from "node:assert"; -import { setTimeout } from "node:timers/promises"; +// import { test } from "test"; +import * as Assert from "assert"; +import { setTimeout } from "timers/promises"; + +import test, { After, asyncMonad, TestFunction } from "arrange-act-assert"; import { isMessage, newRoot } from "./testRunner"; import { MessageType, MessageFileStart, MessageFileEnd, MessageAdded, MessageStart, MessageEnd, Messages, TestType } from "../formatters"; @@ -9,31 +11,25 @@ import { mockFiles } from "../test_folder_mock"; type CheckMessages = MessageFileStart | MessageFileEnd | ({ id:string } & ((Omit & { test: { parentId:string } & Omit }) | Omit | Omit)); -test.describe("testRunner", async () => { - // Arrange - function assert(desc:string, a:unknown, b:unknown) { - if (a !== b) { - console.log("expected:", a); - console.log("found:", b); - throw new Error(`${desc}: Expected ${a} but found ${b}`); - } +test.describe("testRunner", (test) => { + function afterNewRoot(after:After) { + after(process.env.AAA_TEST_FILE, oldAAA => { + process.env.AAA_TEST_FILE = oldAAA + }); + process.env.AAA_TEST_FILE = ""; + return newRoot(); } - function stepped(start = 0) { - let step = start; + function stepped(steps:number[] = []) { return { clone() { - return stepped(step); + return stepped(steps.slice()); }, up(value:number) { - if (step !== value) { - throw new Error(`Step should be ${value} before increasing. Found ${step}`); - } - step++; + steps.push(value); }, assert(value:number) { - if (step !== value) { - throw new Error(`Step should be ${value}. Found ${step}`); - } + const arr = new Array(value + 1).fill(0).map((_, i) => i); + Assert.deepStrictEqual(steps, arr); } } } @@ -49,7 +45,7 @@ test.describe("testRunner", async () => { return { cb: cb, messages: messages, - assert(type:string, check:(CheckMessages)[]) { + assert(check:(CheckMessages)[]) { for (const msg of check) { if ("id" in msg) { msg.id = eval(`${msg.id.startsWith("+") ? firstId : ""}${msg.id}`); @@ -58,43 +54,22 @@ test.describe("testRunner", async () => { } } } - if (JSON.stringify(messages) !== JSON.stringify(check)) { - console.log("Expected:", check); - console.log("Found:", messages); - throw new Error(`${type} logs are different`); - } + Assert.deepStrictEqual(messages, check); messages.splice(0); } }; } - await test.describe("Should run in order", async () => { - test("with X_AFTER", async () => { - const step = stepped(); - const myTest = newRoot(); - await myTest.test("Should run in order with AFTER_X", { - ARRANGE(after) { - step.up(0); - after(null, () => step.up(5)); - }, - ACT(_arrange, after) { - step.up(1); - after(null, () => step.up(4)); - }, - ASSERT(_arrange, _act, after) { - step.up(2); - after(null, () => step.up(3)); - } - }); - }); - test("within describes", async () => { - const step = stepped(); - const myTest = newRoot(); - const descDelayed = myTest.describe("describe delayed", async (test) => { - await setTimeout(10); - await test.test("test delayed", { - ARRANGE(after) { + test.describe("Should run in order", (test) => { + test("ARRANGE -> ACT -> ASSERT", { + ARRANGE(after) { + const step = stepped(); + const myTest = afterNewRoot(after); + return { step, myTest }; + }, + async ACT({ myTest, step }) { + await myTest.test("Should run in order", { + ARRANGE() { step.up(0); - after(null, () => step.up(3)) }, ACT() { step.up(1); @@ -103,256 +78,677 @@ test.describe("testRunner", async () => { step.up(2); } }); - }); - const descDelayed2 = myTest.describe("describe delayed 2", (test) => { - test.test("test delayed", { + }, + ASSERT(_, { step }) { + step.assert(2); + } + }); + test("ARRANGE -> ACT -> ASSERT and reversed afters", { + ARRANGE(after) { + const step = stepped(); + const myTest = afterNewRoot(after); + return { step, myTest }; + }, + async ACT({ myTest, step }) { + await myTest.test("Should run in order with after", { ARRANGE(after) { - step.up(4); - after(null, () => step.up(7)) + step.up(0); + after(null, () => step.up(5)); }, - async ACT() { - step.up(5); - await setTimeout(10) + ACT(_arrange, after) { + step.up(1); + after(null, () => step.up(4)); }, - ASSERT() { - step.up(6); + ASSERT(_arrange, _act, after) { + step.up(2); + after(null, () => step.up(3)); } }); - }); - const desc = myTest.describe("describe", (test) => { - test.test("test delayed", { - ARRANGE(after) { - step.up(8); - after(null, () => step.up(11)) + }, + ASSERT(_, { step }) { + step.assert(5); + } + }); + test("within describes", { + ARRANGE(after) { + const step = stepped(); + const myTest = afterNewRoot(after); + return { step, myTest }; + }, + async ACT({ step, myTest }) { + const descDelayed1 = myTest.describe("describe delayed", async (test) => { + await setTimeout(10); + await test.test("test delayed", { + ARRANGE(after) { + step.up(0); + after(null, () => step.up(3)) + }, + ACT() { + step.up(1); + }, + ASSERT() { + step.up(2); + } + }); + }); + const descDelayed2 = myTest.describe("describe delayed 2", (test) => { + test.test("test delayed", { + ARRANGE(after) { + step.up(4); + after(null, () => step.up(7)) + }, + async ACT() { + step.up(5); + await setTimeout(10) + }, + ASSERT() { + step.up(6); + } + }); + }); + const desc = myTest.describe("describe", (test) => { + test.test("test delayed", { + ARRANGE(after) { + step.up(8); + after(null, () => step.up(11)) + }, + ACT() { + step.up(9); + }, + ASSERT() { + step.up(10); + } + }); + }); + await Promise.all([desc, descDelayed1, descDelayed2]); + }, + ASSERT(_, { step }) { + step.assert(11); + } + }); + test("not allow tests after finishing", { + async ARRANGE() { + const resolvedTest = await new Promise((resolve) => { + newRoot().describe("", test => { + test("empty", { + ASSERT() {} + }); + resolve(test); + }); + }); + await setTimeout(10); // Wait for "finally" resolve + return resolvedTest; + }, + async ACT(resolvedTest) { + return await asyncMonad(() => resolvedTest.test("test", { + ARRANGE() { + return 0; }, ACT() { - step.up(9); + return 0; }, ASSERT() { - step.up(10); + return "ok"; } + })); + }, + ASSERT(res) { + res.should.error({ + message: /This test is closed/ }); - }); - await Promise.all([desc, descDelayed2, descDelayed]); + } }); - test("now allow tests after finishing", async () => { - // Act - let errored = false; - const myTest = newRoot(); - await myTest.describe("describe", test => { - test("empty", { - ASSERT() {} - }); - setTimeout(10).then(() => { - test.test("test", { + }); + test.describe("Should infer data", (test) => { + test.describe("from arrange", (test) => { + test("to act", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res:{act:number|null, assert:number|null} = { + act: null, + assert: null + }; + await myTest.test("", { ARRANGE() { - return 0; + return { pepe: 123 }; }, - ACT() { - return 0; + ACT(arrange) { + res.act = arrange.pepe; + } + }); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + act: 123, + assert: null + }); + } + }); + test("to assert", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res:{act:number|null, assert:number|null} = { + act: null, + assert: null + }; + await myTest.test("", { + ARRANGE() { + return { pepe: 123 }; }, - ASSERT() { - return "ok"; + ASSERT(_, arrange) { + res.assert = arrange.pepe; } - }).catch(() => { - errored = true; }); - }); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + act: null, + assert: 123 + }); + } + }); + test("to act and assert", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res:{act:number|null, assert:number|null} = { + act: null, + assert: null + }; + await myTest.test("", { + ARRANGE() { + return { pepe: 123 }; + }, + ACT(arrange) { + res.act = arrange.pepe; + }, + ASSERT(_, arrange) { + res.assert = arrange.pepe; + } + }); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + act: 123, + assert: 123 + }); + } + }); + test("to after", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res:{act:number|null, assert:number|null, after:number|null} = { + act: null, + assert: null, + after: null + }; + await myTest.test("", { + ARRANGE(after) { + return after({ pepe: 123 }, arrange => { + res.after = arrange.pepe + }); + } + }); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + act: null, + assert: null, + after: 123 + }); + } + }); + test("to after and act", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res:{act:number|null, assert:number|null, after:number|null} = { + act: null, + assert: null, + after: null + }; + await myTest.test("", { + ARRANGE(after) { + return after({ pepe: 123 }, arrange => { + res.after = arrange.pepe + }); + }, + ACT(arrange) { + res.act = arrange.pepe; + } + }); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + act: 123, + assert: null, + after: 123 + }); + } + }); + test("to after and assert", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res:{act:number|null, assert:number|null, after:number|null} = { + act: null, + assert: null, + after: null + }; + await myTest.test("", { + ARRANGE(after) { + return after({ pepe: 123 }, arrange => { + res.after = arrange.pepe + }); + }, + ASSERT(_, arrange) { + res.assert = arrange.pepe; + } + }); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + act: null, + assert: 123, + after: 123 + }); + } + }); + test("to after, act and assert", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res:{act:number|null, assert:number|null, after:number|null} = { + act: null, + assert: null, + after: null + }; + await myTest.test("", { + ARRANGE(after) { + return after({ pepe: 123 }, arrange => { + res.after = arrange.pepe + }); + }, + ACT(arrange) { + res.act = arrange.pepe; + }, + ASSERT(_, arrange) { + res.assert = arrange.pepe; + } + }); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + act: 123, + assert: 123, + after: 123 + }); + } }); - await setTimeout(50); - - // Assert - if (!errored) { - throw new Error("Should error if adding test after finishing"); - } }); - }); - test.describe("Should infer data", () => { - test("from arrange to act, assert and after", async () => { - const myTest = newRoot(); - await myTest.test("test", { + test.describe("from act", (test) => { + test("to assert", { ARRANGE(after) { - return after({ pepe: 123 }, arrange => { - assert("AFTER ARRANGE", arrange.pepe, 123); + return afterNewRoot(after); + }, + async ACT(myTest) { + const res:{assert:number|null} = { + assert: null + }; + await myTest.test("", { + ACT() { + return { pepe: 123 }; + }, + ASSERT(arrange) { + res.assert = arrange.pepe; + } }); + return res; }, - ACT(arrange, after) { - assert("ACT", arrange.pepe, 123); - return after(arrange.pepe + 1, (act) => { - assert("AFTER ACT", act, 124); + ASSERT(res) { + Assert.deepStrictEqual(res, { + assert: 123 }); + } + }); + test("to after", { + ARRANGE(after) { + return afterNewRoot(after); }, - ASSERT(act, arrange, after) { - assert("AssertARRANGE", arrange.pepe, 123); - assert("AssertACT", act, 124); - return after(act + arrange.pepe, (ass) => { - assert("AFTER ASSERT", ass, 123 + 124); + async ACT(myTest) { + const res:{assert:number|null, after:number|null} = { + assert: null, + after: null + }; + await myTest.test("", { + ACT(_, after) { + return after({ pepe: 123 }, arrange => { + res.after = arrange.pepe + }); + } + }); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + assert: null, + after: 123 + }); + } + }); + test("to after and assert", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res:{assert:number|null, after:number|null} = { + assert: null, + after: null + }; + await myTest.test("", { + ACT(_, after) { + return after({ pepe: 123 }, arrange => { + res.after = arrange.pepe + }); + }, + ASSERT(act) { + res.assert = act.pepe; + } + }); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + assert: 123, + after: 123 }); } }); }); + test("from arrange and act to act and assert", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res:{act:number|null, assertAct:number|null, assertArrange:number|null} = { + act: null, + assertAct: null, + assertArrange: null, + }; + await myTest.test("", { + ARRANGE() { + return { pepe: 123 }; + }, + ACT(arrange) { + res.act = arrange.pepe; + return { pepe2: 124 } + }, + ASSERT(act, arrange) { + res.assertAct = act.pepe2; + res.assertArrange = arrange.pepe; + } + }); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + act: 123, + assertAct: 124, + assertArrange: 123 + }); + } + }); + test("from assert to after", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res:{after:number|null} = { + after: null + }; + await myTest.test("", { + ASSERT(_act, _arr, after) { + after({ pepe: 123 }, assert => { + res.after = assert.pepe; + }) + } + }); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + after: 123 + }); + } + }); }); - test.describe("Error managing", () => { - test("should throw error if describe fails", async () => { - const myTest = newRoot(); - try { - await myTest.describe("describe", () => { - throw "ok"; + test.describe("Error managing", (test) => { + test("should throw error if describe fails", { + ARRANGE(after) { + return afterNewRoot(after); + }, + ACT(myTest) { + const promise = myTest.describe("describe", () => { + throw new Error("ok"); + }); + return { promise }; + }, + async ASSERT({ promise }) { + await Assert.rejects(promise, { + message: "ok" }); - throw new Error("Should throw error"); - } catch (e) { - if (e !== "ok") { - throw e; - } } }); - test("should not call act/assert 'after' if arrange fails", async () => { - let validAfterCalled = 0; - let invalidAfterCalled = 0; - const myTest = newRoot(); - try { - await myTest.test("test", { + test("should not call act/assert 'after()' if arrange fails", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res = { + valid: 0, + invalid: 0 + }; + await asyncMonad(() => myTest.test("test", { ARRANGE(after) { - after(null, () => validAfterCalled++); + after(null, () => res.valid++); throw "ok"; }, ACT(_arr, after) { - after(null, () => invalidAfterCalled++); - return 0; + after(null, () => res.invalid++); }, ASSERT(_act, _arr, after) { - after(null, () => invalidAfterCalled++); - return 0; + after(null, () => res.invalid++); } + })); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + valid: 1, + invalid: 0 }); - throw new Error("Should throw error"); - } catch (e) { - if (e !== "ok") { - throw e; - } } - Assert.strictEqual(validAfterCalled, 1, "Valid after should be called"); - Assert.strictEqual(invalidAfterCalled, 0, "Invalid afters should not be called"); }); - test("should not call assert 'after' if act fails", async () => { - let validAfterCalled = 0; - let invalidAfterCalled = 0; - const myTest = newRoot(); - try { - await myTest.test("test", { + test("should not call assert 'after' if act fails", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res = { + valid: 0, + invalid: 0 + }; + await asyncMonad(() => myTest.test("test", { ARRANGE(after) { - after(null, () => validAfterCalled++); - return 0; + after(null, () => res.valid++); }, ACT(_arr, after) { - after(null, () => validAfterCalled++); + after(null, () => res.valid++); throw "ok"; }, ASSERT(_act, _arr, after) { - after(null, () => invalidAfterCalled++); - return 0; + after(null, () => res.invalid++); } + })); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + valid: 2, + invalid: 0 }); - throw new Error("Should throw error"); - } catch (e) { - if (e !== "ok") { - throw e; - } } - Assert.strictEqual(validAfterCalled, 2, "Valid after should be called"); - Assert.strictEqual(invalidAfterCalled, 0, "Invalid afters should not be called"); }); - test("should call all 'afters' if arrange_after fails", async () => { - let validAfterCalled = 0; - const myTest = newRoot(); - try { - await myTest.test("test", { + test("should call all 'afters' if arrange_after fails", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res = { + valid: 0, + invalid: 0 + }; + await asyncMonad(() => myTest.test("test", { + ARRANGE(after) { + after(null, () => res.valid++); + }, + ACT(_arr, after) { + after(null, () => res.valid++); + }, + ASSERT(_act, _arr, after) { + after(null, () => res.valid++); + throw "ok"; + } + })); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + valid: 3, + invalid: 0 + }); + } + }); + test("should call all 'afters' if arrange_after fails", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res = { + valid: 0, + invalid: 0 + }; + await asyncMonad(() => myTest.test("test", { ARRANGE(after) { after(null, () => { throw "ok"; }); - return 0; }, ACT(_arr, after) { - after(null, () => validAfterCalled++); - return 0; + after(null, () => res.valid++); }, ASSERT(_act, _arr, after) { - after(null, () => validAfterCalled++); - return 0; + after(null, () => res.valid++); } + })); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + valid: 2, + invalid: 0 }); - throw new Error("Should throw error"); - } catch (e) { - if (e !== "ok") { - throw e; - } } - Assert.strictEqual(validAfterCalled, 2, "Valid after should be called"); }); - test("should call all 'afters' if act_after fails", async () => { - let validAfterCalled = 0; - const myTest = newRoot(); - try { - await myTest.test("test", { + test("should call all 'afters' if act_after fails", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res = { + valid: 0, + invalid: 0 + }; + await asyncMonad(() => myTest.test("test", { ARRANGE(after) { - after(null, () => validAfterCalled++); - return 0; + after(null, () => res.valid++); }, ACT(_arr, after) { after(null, () => { throw "ok"; }); - return 0; }, ASSERT(_act, _arr, after) { - after(null, () => validAfterCalled++); - return 0; + after(null, () => res.valid++); } + })); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + valid: 2, + invalid: 0 }); - throw new Error("Should throw error"); - } catch (e) { - if (e !== "ok") { - throw e; - } } - Assert.strictEqual(validAfterCalled, 2, "Valid after should be called"); }); - test("should call all 'afters' if assert_after fails", async () => { - let validAfterCalled = 0; - const myTest = newRoot(); - try { - await myTest.test("test", { + test("should call all 'afters' if assert_after fails", { + ARRANGE(after) { + return afterNewRoot(after); + }, + async ACT(myTest) { + const res = { + valid: 0, + invalid: 0 + }; + await asyncMonad(() => myTest.test("test", { ARRANGE(after) { - after(null, () => validAfterCalled++); - return 0; + after(null, () => res.valid++); }, ACT(_arr, after) { - after(null, () => validAfterCalled++); - return 0; + after(null, () => res.valid++); }, ASSERT(_act, _arr, after) { after(null, () => { throw "ok"; }); - return 0; } + })); + return res; + }, + ASSERT(res) { + Assert.deepStrictEqual(res, { + valid: 2, + invalid: 0 }); - throw new Error("Should throw error"); - } catch (e) { - if (e !== "ok") { - throw e; - } } - Assert.strictEqual(validAfterCalled, 2, "Valid after should be called"); }); }); - test.describe("Should notify parent process", () => { - // Arrange - function getProcessSend() { - const oldAAA = process.env.AAA_TEST_FILE; + test.describe("Should notify parent process", (test) => { + function getProcessSend(after:After) { + after(process.env.AAA_TEST_FILE, oldAAA => { + process.env.AAA_TEST_FILE = oldAAA + }); process.env.AAA_TEST_FILE = "1"; - const oldSend = process.send; + after(process.send, oldSend => { + process.send = oldSend + }); const formatter = getFormatter(); process.send = (msg:unknown) => { if (isMessage(msg)) { @@ -360,94 +756,117 @@ test.describe("testRunner", async () => { } return true; }; - test.after(() => { - process.env.AAA_TEST_FILE = oldAAA; - process.send = oldSend; - }); const root = newRoot(); return { formatter, root }; } - test.test("should work if no existing process", async () => { - // Arrange - const myTest = newRoot(); - const oldProcess = global.process; - global.process = undefined as any; - test.after(() => { - global.process = oldProcess; - newRoot(); - }); - - // Act/Assert (should not crash) - await myTest.test("test", { - ARRANGE() { - return 0; - }, - ACT() { - return 0; - }, - ASSERT() { - return "ok"; - } - }); + test.test("process.send is called", { + ARRANGE(after) { + return getProcessSend(after); + }, + async ACT({ root }) { + await root.test("test", { + ARRANGE() { + return 0; + }, + ACT() { + return 0; + }, + ASSERT() { + return "ok"; + } + }); + }, + ASSERT(_, { formatter, root }) { + formatter.assert([{ + id: "+0", + type: MessageType.ADDED, + test: { + parentId: String(root.id), + description: "test", + type: TestType.TEST + } + }, { + id: "+0", + type: MessageType.START + }, { + id: "+1", + type: MessageType.ADDED, + test: { + parentId: "+0", + description: "", + type: TestType.ASSERT + } + }, { + id: "+1", + type: MessageType.START + }, { + id: "+1", + type: MessageType.END + }, { + id: "+0", + type: MessageType.END + }]); + } }); - - test.test("process.send is called", async () => { - // Arrange - const { formatter, root } = getProcessSend(); - - // Act - await root.test("test", { - ARRANGE() { - return 0; - }, - ACT() { - return 0; - }, - ASSERT() { - return "ok"; - } - }); - - // Assert - formatter.assert("after test", [{ - id: "+0", - type: MessageType.ADDED, - test: { - parentId: String(root.id), - description: "test", - type: TestType.TEST - } - }, { - id: "+0", - type: MessageType.START - }, { - id: "+1", - type: MessageType.ADDED, - test: { - parentId: "+0", - description: "", - type: TestType.ASSERT - } - }, { - id: "+1", - type: MessageType.START - }, { - id: "+1", - type: MessageType.END - }, { - id: "+0", - type: MessageType.END - }]); + test.test("end is called only once if a test is added after finish", { + ARRANGE(after) { + return getProcessSend(after); + }, + async ACT({ root }) { + await root.describe("describe", test => { + setTimeout(10).then(() => { + test.test("test", { + ARRANGE() { + return 0; + }, + ACT() { + return 0; + }, + ASSERT() { + return "ok"; + } + }).catch(() => {}); + }); + }); + await setTimeout(50); + }, + ASSERT(_, { formatter, root }) { + formatter.assert([{ + id: "+0", + type: MessageType.ADDED, + test: { + parentId: String(root.id), + description: "describe", + type: TestType.DESCRIBE + } + }, { + id: "+0", + type: MessageType.START + }, { + id: "+0", + type: MessageType.END + }]); + } }); - - test.test("end is called only once if a test is added after finish", async () => { - // Arrange - const { formatter, root } = getProcessSend(); - - // Act - await root.describe("describe", test => { - setTimeout(10).then(() => { - test.test("test", { + test.test("describe end is called only after tests are ended", { + ARRANGE(after) { + return getProcessSend(after); + }, + async ACT({ root }) { + await root.describe("describe", test => { + test.test("test1", { + ARRANGE() { + return 0; + }, + ACT() { + return 0; + }, + async ASSERT() { + await setTimeout(20); + return "ok"; + } + }).catch(() => {}); + test.test("test2", { ARRANGE() { return 0; }, @@ -459,356 +878,309 @@ test.describe("testRunner", async () => { } }).catch(() => {}); }); - }); - await setTimeout(50); - - // Assert - formatter.assert("after test", [{ - id: "+0", - type: MessageType.ADDED, - test: { - parentId: String(root.id), - description: "describe", - type: TestType.DESCRIBE - } - }, { - id: "+0", - type: MessageType.START - }, { - id: "+0", - type: MessageType.END - }]); - }); - - test.test("describe end is called only after tests are ended", async () => { - // Arrange - const { formatter, root } = getProcessSend(); - - // Act - await root.describe("describe", test => { - test.test("test1", { - ARRANGE() { - return 0; - }, - ACT() { - return 0; - }, - async ASSERT() { - await setTimeout(20); - return "ok"; + await setTimeout(50); + }, + ASSERT(_, { formatter, root }) { + formatter.assert([{ + id: "+0", + type: MessageType.ADDED, + test: { + parentId: String(root.id), + description: "describe", + type: TestType.DESCRIBE } - }).catch(() => {}); - test.test("test2", { - ARRANGE() { - return 0; - }, - ACT() { - return 0; - }, - ASSERT() { - return "ok"; + }, { + id: "+0", + type: MessageType.START + }, { + id: "+1", + type: MessageType.ADDED, + test: { + parentId: "+0", + description: "test1", + type: TestType.TEST } - }).catch(() => {}); - }); - - // Assert - formatter.assert("after test", [{ - id: "+0", - type: MessageType.ADDED, - test: { - parentId: String(root.id), - description: "describe", - type: TestType.DESCRIBE - } - }, { - id: "+0", - type: MessageType.START - }, { - id: "+1", - type: MessageType.ADDED, - test: { - parentId: "+0", - description: "test1", - type: TestType.TEST - } - }, { - id: "+1", - type: MessageType.START - }, { - id: "+2", - type: MessageType.ADDED, - test: { - parentId: "+0", - description: "test2", - type: TestType.TEST - } - }, { - id: "+3", - type: MessageType.ADDED, - test: { - parentId: "+1", - description: "", - type: TestType.ASSERT - } - }, { - id: "+3", - type: MessageType.START - }, { - id: "+3", - type: MessageType.END - }, { - id: "+1", - type: MessageType.END - }, { - id: "+2", - type: MessageType.START - }, { - id: "+4", - type: MessageType.ADDED, - test: { - parentId: "+2", - description: "", - type: TestType.ASSERT - } - }, { - id: "+4", - type: MessageType.START - }, { - id: "+4", - type: MessageType.END - }, { - id: "+2", - type: MessageType.END - }, { - id: "+0", - type: MessageType.END - }]); - }); - - test("should show nested error logs", async () => { - // Arrange - const { formatter, root } = getProcessSend(); - - // Act - const promise = root.describe("describe", test => { - test.test("test1", { - ARRANGE() { - return 0; - }, - ACT() { - return 0; - }, - async ASSERT() { - await setTimeout(20); - throw "ok"; + }, { + id: "+1", + type: MessageType.START + }, { + id: "+2", + type: MessageType.ADDED, + test: { + parentId: "+0", + description: "test2", + type: TestType.TEST } - }).catch(() => {}); - test.test("test2", { - ARRANGE() { - return 0; - }, - ACT() { - return 0; - }, - ASSERT() { - return "ok"; + }, { + id: "+3", + type: MessageType.ADDED, + test: { + parentId: "+1", + description: "", + type: TestType.ASSERT } - }).catch(() => {}); - }); - - // Assert - await Assert.rejects(promise, e => e === "ok"); - formatter.assert("after test", [{ - id: "+0", - type: MessageType.ADDED, - test: { - parentId: String(root.id), - description: "describe", - type: TestType.DESCRIBE - } - }, { - id: "+0", - type: MessageType.START - }, { - id: "+1", - type: MessageType.ADDED, - test: { - parentId: "+0", - description: "test1", - type: TestType.TEST - } - }, { - id: "+1", - type: MessageType.START - }, { - id: "+2", - type: MessageType.ADDED, - test: { - parentId: "+0", - description: "test2", - type: TestType.TEST - } - }, { - id: "+3", - type: MessageType.ADDED, - test: { - parentId: "+1", - description: "", - type: TestType.ASSERT - } - }, { - id: "+3", - type: MessageType.START - }, { - id: "+3", - type: MessageType.END, - error: "ok" - }, { - id: "+1", - type: MessageType.END, - error: "ok" - }, { - id: "+2", - type: MessageType.START - }, { - id: "+4", - type: MessageType.ADDED, - test: { - parentId: "+2", - description: "", - type: TestType.ASSERT + }, { + id: "+3", + type: MessageType.START + }, { + id: "+3", + type: MessageType.END + }, { + id: "+1", + type: MessageType.END + }, { + id: "+2", + type: MessageType.START + }, { + id: "+4", + type: MessageType.ADDED, + test: { + parentId: "+2", + description: "", + type: TestType.ASSERT + } + }, { + id: "+4", + type: MessageType.START + }, { + id: "+4", + type: MessageType.END + }, { + id: "+2", + type: MessageType.END + }, { + id: "+0", + type: MessageType.END + }]); + } + }); + test("should show nested error logs", { + ARRANGE(after) { + return getProcessSend(after); + }, + ACT({ root }) { + const promise = root.describe("describe", test => { + test.test("test1", { + ARRANGE() { + return 0; + }, + ACT() { + return 0; + }, + async ASSERT() { + await setTimeout(20); + throw "ok"; + } + }).catch(() => {}); + test.test("test2", { + ARRANGE() { + return 0; + }, + ACT() { + return 0; + }, + ASSERT() { + return "ok"; + } + }).catch(() => {}); + }); + return { promise }; + }, + ASSERTS: { + async "promise should fail with error"({ promise }) { + await Assert.rejects(promise, e => e === "ok"); + }, + "formatter should receive the events in order"(_, { formatter, root }) { + formatter.assert([{ + id: "+0", + type: MessageType.ADDED, + test: { + parentId: String(root.id), + description: "describe", + type: TestType.DESCRIBE + } + }, { + id: "+0", + type: MessageType.START + }, { + id: "+1", + type: MessageType.ADDED, + test: { + parentId: "+0", + description: "test1", + type: TestType.TEST + } + }, { + id: "+1", + type: MessageType.START + }, { + id: "+2", + type: MessageType.ADDED, + test: { + parentId: "+0", + description: "test2", + type: TestType.TEST + } + }, { + id: "+3", + type: MessageType.ADDED, + test: { + parentId: "+1", + description: "", + type: TestType.ASSERT + } + }, { + id: "+3", + type: MessageType.START + }, { + id: "+3", + type: MessageType.END, + error: "ok" + }, { + id: "+1", + type: MessageType.END, + error: "ok" + }, { + id: "+2", + type: MessageType.START + }, { + id: "+4", + type: MessageType.ADDED, + test: { + parentId: "+2", + description: "", + type: TestType.ASSERT + } + }, { + id: "+4", + type: MessageType.START + }, { + id: "+4", + type: MessageType.END + }, { + id: "+2", + type: MessageType.END + }, { + id: "+0", + type: MessageType.END, + error: "ok" + }]); } - }, { - id: "+4", - type: MessageType.START - }, { - id: "+4", - type: MessageType.END - }, { - id: "+2", - type: MessageType.END - }, { - id: "+0", - type: MessageType.END, - error: "ok" - }]); + } }); }); - test.describe("Should run test files", () => { - // Assert - function newFormatter() { + test.describe("Should run test files", (test) => { + function newFormatter(after:After) { const formatter = getFormatter(); - const root = newRoot(); + const root = afterNewRoot(after); root.setFormatter({ format: (_fileId, msg) => { formatter.cb(msg); } }); - test.after(() => newRoot()); return { formatter, root }; } - async function runTest(spawn = false) { - // Arrange - const { formatter, root } = newFormatter(); - const rootId = spawn ? "0" : root.id; - const check:CheckMessages[] = [{ - id: "+0", - type: MessageType.ADDED, - test: { - parentId: String(rootId), - description: "assertNumber1", - type: TestType.DESCRIBE - } - }, { - id: "+0", - type: MessageType.START - }, { - id: "+1", - type: MessageType.ADDED, - test: { - parentId: "+0", - description: "should work", - type: TestType.TEST - } - }, { - id: "+1", - type: MessageType.START - }, { - id: "+2", - type: MessageType.ADDED, - test: { - parentId: "+0", - description: "should not work", - type: TestType.TEST - } - }, { - id: "+3", - type: MessageType.ADDED, - test: { - parentId: "+1", - description: "", - type: TestType.ASSERT - } - }, { - id: "+3", - type: MessageType.START - }, { - id: "+3", - type: MessageType.END - }, { - id: "+1", - type: MessageType.END - }, { - id: "+2", - type: MessageType.START - }, { - id: "+4", - type: MessageType.ADDED, - test: { - parentId: "+2", - description: "", - type: TestType.ASSERT + async function runTest(testName:string, spawn = false) { + test(testName, { + ARRANGE(after) { + return newFormatter(after); + }, + async ACT({ root }) { + if (spawn) { + await root.spawnTestFile(mockFiles["file1.mytest-ok"], { prefix: [] }); + } else { + root.runTestFile(mockFiles["file1.mytest-ok"], { + clearModuleCache: true + }); + } + await root.run(); + }, + ASSERT(_, { formatter, root }) { + const rootId = spawn ? "0" : root.id; + const check:CheckMessages[] = [{ + id: "+0", + type: MessageType.ADDED, + test: { + parentId: String(rootId), + description: "assertNumber1", + type: TestType.DESCRIBE + } + }, { + id: "+0", + type: MessageType.START + }, { + id: "+1", + type: MessageType.ADDED, + test: { + parentId: "+0", + description: "should work", + type: TestType.TEST + } + }, { + id: "+1", + type: MessageType.START + }, { + id: "+2", + type: MessageType.ADDED, + test: { + parentId: "+0", + description: "should not work", + type: TestType.TEST + } + }, { + id: "+3", + type: MessageType.ADDED, + test: { + parentId: "+1", + description: "", + type: TestType.ASSERT + } + }, { + id: "+3", + type: MessageType.START + }, { + id: "+3", + type: MessageType.END + }, { + id: "+1", + type: MessageType.END + }, { + id: "+2", + type: MessageType.START + }, { + id: "+4", + type: MessageType.ADDED, + test: { + parentId: "+2", + description: "", + type: TestType.ASSERT + } + }, { + id: "+4", + type: MessageType.START + }, { + id: "+4", + type: MessageType.END + }, { + id: "+2", + type: MessageType.END + }, { + id: "+0", + type: MessageType.END + }]; + if (spawn) { + check.unshift({ + type: MessageType.FILE_START + }); + check.push({ + type: MessageType.FILE_END + }); + } + formatter.assert(check); } - }, { - id: "+4", - type: MessageType.START - }, { - id: "+4", - type: MessageType.END - }, { - id: "+2", - type: MessageType.END - }, { - id: "+0", - type: MessageType.END - }]; - if (spawn) { - check.unshift({ - type: MessageType.FILE_START - }); - check.push({ - type: MessageType.FILE_END - }); - } - - // Act - if (spawn) { - await root.spawnTestFile(mockFiles["file1.mytest-ok"], { prefix: [] }); - } else { - root.runTestFile(mockFiles["file1.mytest-ok"], { - clearModuleCache: true - }); - } - await root.run(); - - // Assert - formatter.assert("after test", check); + }); } - test("should run a test file", async () => { - await runTest(); - }); - test("should spawn a test file", async () => { - await runTest(true); - }); + runTest("should run a test file"); + runTest("should spawn a test file", true); }); }); \ No newline at end of file diff --git a/src/test_folder_mock/file1.mytest-ok.ts b/src/test_folder_mock/file1.mytest-ok.ts index 4380800..175d78a 100644 --- a/src/test_folder_mock/file1.mytest-ok.ts +++ b/src/test_folder_mock/file1.mytest-ok.ts @@ -1,4 +1,4 @@ -import * as Assert from "node:assert"; +import * as Assert from "assert"; import test from ".."; import { assertNumber1 } from "./file1"; diff --git a/src/test_folder_mock/file1.ts b/src/test_folder_mock/file1.ts index 547f364..652baa3 100644 --- a/src/test_folder_mock/file1.ts +++ b/src/test_folder_mock/file1.ts @@ -1,4 +1,4 @@ -import * as Assert from "node:assert"; +import * as Assert from "assert"; export function assertNumber1(n:number) { Assert.strictEqual(n, 1, "is not number 1"); diff --git a/src/test_folder_mock/test_subfolder_mock/file2.mytest-ok.ts b/src/test_folder_mock/test_subfolder_mock/file2.mytest-ok.ts index 7b1e107..0db911d 100644 --- a/src/test_folder_mock/test_subfolder_mock/file2.mytest-ok.ts +++ b/src/test_folder_mock/test_subfolder_mock/file2.mytest-ok.ts @@ -1,4 +1,4 @@ -import * as Assert from "node:assert"; +import * as Assert from "assert"; import test from "../.."; import { assertNumber2 } from "./file2"; diff --git a/src/test_folder_mock/test_subfolder_mock/file2.ts b/src/test_folder_mock/test_subfolder_mock/file2.ts index d95e2ec..951fd01 100644 --- a/src/test_folder_mock/test_subfolder_mock/file2.ts +++ b/src/test_folder_mock/test_subfolder_mock/file2.ts @@ -1,4 +1,4 @@ -import * as Assert from "node:assert"; +import * as Assert from "assert"; export function assertNumber2(n:number) { Assert.strictEqual(n, 2, "is not number 2"); diff --git a/src/utils/utils.test.ts b/src/utils/utils.test.ts index d5a67d7..3f187a1 100644 --- a/src/utils/utils.test.ts +++ b/src/utils/utils.test.ts @@ -1,76 +1,127 @@ -import { test } from "node:test"; -import * as Assert from "node:assert"; +import * as Assert from "assert"; +import { monad, test } from "arrange-act-assert"; import { resolvablePromise, clearModuleCache, processArgs } from "./utils"; import { mockFiles } from "../test_folder_mock"; -test.describe("utils", () => { - test.describe("newPromise", () => { - test("newPromise should resolve", async () => { - const promise = resolvablePromise(); - promise.resolve(); - await promise; +test.describe("utils", (test) => { + test.describe("newPromise", (test) => { + test("should resolve", { + ARRANGE() { + const promise = resolvablePromise(); + return { promise }; + }, + ACT({ promise }) { + promise.resolve(); + }, + async ASSERT(_, { promise }) { + await Assert.doesNotReject(promise); + } }); - test("newPromise couldn't resolve 2 times", async () => { - const promise = resolvablePromise(); - promise.resolve(); - promise.resolve(); - await promise; + test("should handle resolve 2 times", { + ARRANGE() { + const promise = resolvablePromise(); + return { promise }; + }, + ACT({ promise }) { + promise.resolve(); + promise.resolve(); + }, + async ASSERT(_, { promise }) { + await Assert.doesNotReject(promise); + } }); - test("newPromise should reject", async () => { - const promise = resolvablePromise(); - promise.reject(new Error("ok")); - await Assert.rejects(promise, { - message: "ok" - }); + test("should reject", { + ARRANGE() { + const promise = resolvablePromise(); + return { promise }; + }, + ACT({ promise }) { + promise.reject(new Error("ok")); + }, + async ASSERT(_, { promise }) { + await Assert.rejects(promise, { + message: "ok" + }); + } }); - test("newPromise should throw error if rejected after resolve", async () => { - const promise = resolvablePromise(); - promise.resolve(); - await promise; - Assert.throws(() => promise.reject(new Error("ok")), { - message: "Already resolved" - }); + test("should throw error if rejected after resolve", { + async ARRANGE() { + const promise = resolvablePromise(); + promise.resolve(); + await promise; + return { promise }; + }, + ACT({ promise }) { + return monad(() => promise.reject(new Error("ok"))); + }, + ASSERT(res) { + res.should.error({ + message: "Already resolved" + }); + } }); - test("newPromise should not throw error if already rejected", async () => { - const promise = resolvablePromise(); - promise.reject(new Error("ok")); - await Assert.rejects(promise, { - message: "ok" - }); - promise.reject(new Error("ok2")); + test("should not throw error if already rejected", { + async ARRANGE() { + const promise = resolvablePromise(); + promise.reject(new Error("ok")); + await Assert.rejects(promise, { + message: "ok" + }); + return { promise }; + }, + ACT({ promise }) { + promise.reject(new Error("ok2")); + } }); }); - test.describe("should clear module cache", () => { - // Act - test.after(() => clearModuleCache(mockFiles["re-evaluation"])); - const result1 = require(mockFiles["re-evaluation"]); - const result2 = require(mockFiles["re-evaluation"]); - clearModuleCache(mockFiles["re-evaluation"]); - const result3 = require(mockFiles["re-evaluation"]); - const result4 = require(mockFiles["re-evaluation"]); - - // Assert - Assert.strictEqual(result1, result2, "should be equal"); - Assert.strictEqual(result3, result4, "should be equal"); - Assert.notStrictEqual(result2, result3, "should not be equal"); - }); - test.describe("should process args", () => { - // Act - const args = processArgs(["--src", "mySrc", "--multi", "first", "--empty", "--multi", "second", "--multi", "third", "--emptyFinal"]); - - // Assert - Assert.deepStrictEqual(Array.from(args.entries()), [ - ["src", ["mySrc"]], - ["multi", ["first", "second", "third"]], - ["empty", [""]], - ["emptyFinal", [""]] - ]); + test("clearModuleCache", { + ARRANGE(after) { + after(null, () => clearModuleCache(mockFiles["re-evaluation"])); + }, + ACT() { + const result1 = require(mockFiles["re-evaluation"]); + const result2 = require(mockFiles["re-evaluation"]); + clearModuleCache(mockFiles["re-evaluation"]); + const result3 = require(mockFiles["re-evaluation"]); + const result4 = require(mockFiles["re-evaluation"]); + return { result1, result2, result3, result4 }; + }, + ASSERTS: { + "before clear should be equal"({ result1, result2 }) { + Assert.strictEqual(result1, result2); + }, + "after clear should be equal"({ result3, result4 }) { + Assert.strictEqual(result3, result4); + }, + "before and after clear should not be equal"({ result2, result3 }) { + Assert.notStrictEqual(result2, result3); + }, + } }); - test.describe("should error on invalid args", () => { - // Act//Assert - Assert.throws(() => processArgs(["--src", "mySrc", "--multi", "first", "errorArg"]), { - message: "Invalid argument: errorArg" + test.describe("processArgs", (test) => { + test("should process args", { + ACT() { + return processArgs(["--src", "mySrc", "--multi", "first", "--empty", "--multi", "second", "--multi", "third", "--emptyFinal"]); + }, + ASSERT(args) { + Assert.deepStrictEqual(Array.from(args.entries()), [ + ["src", ["mySrc"]], + ["multi", ["first", "second", "third"]], + ["empty", [""]], + ["emptyFinal", [""]] + ]); + } + }); + test("should error on invalid args", { + ACT() { + return monad(() => processArgs(["--src", "mySrc", "--multi", "first", "errorArg"])); + }, + ASSERT(res) { + res.should.error({ + message: "Invalid argument: errorArg" + }); + } }); }); }); \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 1705893..4177376 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -48,7 +48,6 @@ export function clearModuleCache(file:string, _root = PATH.dirname(file) + PATH. } export function processArgs(args:string[]) { - // TODO: Test this let nextKey = ""; const res = new Map(); for (const arg of args) {