{"id":16906068,"url":"https://github.com/tylors/45","last_synced_at":"2025-10-30T19:47:57.500Z","repository":{"id":57678756,"uuid":"80311029","full_name":"TylorS/45","owner":"TylorS","description":"A Functional, monadic test-runner","archived":false,"fork":false,"pushed_at":"2017-03-03T13:01:33.000Z","size":154,"stargazers_count":26,"open_issues_count":0,"forks_count":4,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-01-13T07:05:23.299Z","etag":null,"topics":["assertions","functional-programming","lazy","monad","test","test-runner"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TylorS.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-28T21:10:46.000Z","updated_at":"2023-09-03T19:39:42.000Z","dependencies_parsed_at":"2022-09-14T09:21:22.757Z","dependency_job_id":null,"html_url":"https://github.com/TylorS/45","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylorS%2F45","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylorS%2F45/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylorS%2F45/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylorS%2F45/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TylorS","download_url":"https://codeload.github.com/TylorS/45/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233707309,"owners_count":18717413,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["assertions","functional-programming","lazy","monad","test","test-runner"],"created_at":"2024-10-13T18:40:49.678Z","updated_at":"2025-09-21T03:31:36.447Z","avatar_url":"https://github.com/TylorS.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 45\n\n\u003e A functionally-oriented test runner\n\n`45` is the fast and functional test runner that is easy to use and gets out of\nyour way.\n\n## Let me have it!\n```sh\nnpm install --save-dev 45\n```\n\n## Features\n\n- Does not rely on globals\n- Test failure if no assertions are returned\n- Lazy, monadic, and curried test assertions via [`4.5`](https://github.com/TylorS/4.5)\n- Promise, Observable, and Async/Await support\n- Runs all tests in parallel\n- ES2015 and TypeScript support out-of-box\n\n## Basic Usage\n\nCreate a test file\n\n```js\n// test/foo.js\n\n// ES2015\nimport { describe, given, it, equals } from '45';\n\nexport const test = describe('Array', [\n  given('a few numbers', [\n    it('has length greater than 0', () =\u003e {\n      return equals([1, 2, 3].length \u003e 0, true);\n    })\n  ])\n])\n\nexport const otherTest = it('equals 4', () =\u003e {\n  return equals(4, 4);\n})\n\n// commonjs\nconst { describe, given, it, equals } = require('45');\n\nexports.test = describe('Array', [\n  given('a few numbers', [\n    it('has length greater than 0', () =\u003e {\n      return equals([1, 2, 3].length \u003e 0, true);\n    })\n  ])\n])\n\nexports.otherTest = it('equals 4', () =\u003e {\n  return equals(4, 4);\n})\n```\n\nIn your terminal run\n\n```sh\n./node_modules/.bin/45 test/foo.js\n# Supports globs\n./node_modules/.bin/45 test/*.js\n\n# or without parameters\n# by default it will search for all .test and .spec files in src/ folder\n# and for all files in test/ and tests/ folders\n./node_modules/.bin/45\n```\n\nAnd you should see:\n\n![basic-test](./.assets/basic_test.png)\n\n## Tests\n\nWhen running 45 from the command line, it will look for all test files\nit can, collecting all exports, of no particular export name, that adhere\nto the `Test` interface described in the [types](#types) section. This means\nthat 45 can be extended to handle new test types not offered here via 3rd\nparty libraries.\n\nAll 45 `Test`s must return objects adhering to the `Assertion` interface\ndescribed in the [Types](#types) section. Many assertions are re-exported by this\nlibrary from [4.5](https://github.com/TylorS/4.5). Though a number are provided\nby default -- 3rd party libraries implementing the `Assertion` interface can be used\n100% freely.\n\n#### `describe(thing: string, tests: Array\u003cTest\u003e): Test`\n\nAllows collecting many `Test`s together as a larger whole. All tests are\nrun in parallel.\n\n```typescript\nimport { describe } from '45';\n\nexport const test = describe('My Thing', [ ... ])\n```\n\n#### `given(parameters: string, tests: Array\u003cTest\u003e): Test`\n\nJust like `describe` in that it allows collecting many `Test`s together as a\nlarger whole, but allows for more descriptive test suites. All tests are run in\nparallel.\n\n```typescript\nimport { describe, given } from '45';\n\nexport const test = describe('My thing', [\n  given(`a b and c`, [\n    it('does ...', () =\u003e { ... })\n  ])\n])\n```\n\n#### `it(does: string, testFn: TestFn): Test`\n\nPrimitive test type which allows providing a callback to\nactually perform assertions. By default, will timeout at 2000 milliseconds.\n\n```typescript\nimport { it, pass } from '45';\n\nexport const test = it('does things', () =\u003e {\n  return pass(1);\n})\n```\n\n#### `timeout(ms: number, test: Test): Test`\n\nAllows adjusting the amount of time a test can take to complete.\n\n```typescript\nimport { it, timeout, equals } from '45';\n\nexport const failing = it('fails', () =\u003e {\n  return new Promise((resolve) =\u003e {\n    setTimeout(resolve, 3000, 1)\n  })\n    .then(equals(1));\n})\n\nexport const passing = timeout(3500, it('passes', () =\u003e {\n  return new Promise((resolve) =\u003e {\n    setTimeout(resolve, 3000, 1)\n  })\n    .then(equals(1));\n}));\n```\n\n#### `beforeEach(hook: () =\u003e any, tests: Array\u003cTest\u003e): Test`\n\nAllow running a hook before a series of tests. This will run the containing array\nof tests one after another.\n\n```typescript\nimport { beforeEach, describe, given, it } from './';\n\nimport { equals } from '4.5';\n\nlet x = 0;\n\nexport const test = describe('beforeEach', [\n  given(`a function and an array of tests`, [\n    beforeEach(() =\u003e { x++; }, [\n      it('runs beforeEach test', () =\u003e {\n        return equals(1, x);\n      }),\n\n      it('runs beforeEach test every time', () =\u003e {\n        return equals(2, x);\n      }),\n    ]),\n  ]),\n]);\n```\n\n## Assertions (re-exported from 4.5)\n\n- All functions of arity 2 or more are curried.\n- All types are defined below in the [types](#types) section.\n\n#### `equals\u003cA\u003e(expected: A, actual: A): Assertion\u003cA\u003e`\n\nAsserts two values `expected` and `actual` to have value equality.\n\n#### `is\u003cA\u003e(expected: A, actual: A): Assertion\u003cA\u003e`\n\nAsserts two values `expected` and `actual` to have referential equality.\n\n#### `pass\u003cA\u003e(value: A): Assertion\u003cA\u003e`\n\nCreates an assertion which always passes with a given value.\n\n#### `fail(message: any): Assertion\u003cvoid\u003e`\n\nCreates an assertion which will always fail with a given message.\n\n#### `throws(fn: () =\u003e any): Assertion\u003cError\u003e`\n\nCreates an assertion that tests that a given function throws and error.\n\n## Assertion combinators (re-exported from 4.5)\n\n#### `map\u003cA, B\u003e(fn: (a: A) =\u003e B, assertion: Assertion\u003cA\u003e): Assertion\u003cB\u003e`\n\nGiven a function it maps one assertion value to another.\n\n```typescript\nimport { it, map, equals } from '45';\n\nexport const test = it('maps a value from type A to type B', () =\u003e {\n  const add1 = (x: number) =\u003e x + 1;\n\n  return map(add1 /* called with 1 */, equals(1, 1)) // Assertion\u003c2\u003e;\n});\n```\n\n#### `ap\u003cA, B\u003e(fn: Assertion\u003c(a: A) =\u003e B\u003e, value: Assertion\u003cA\u003e): Assertion\u003cB\u003e`\n\nGiven an assertion containing a function from `a` to `b`, and an assertion\nof `a` returns an assertion of type `b`.\n\n```typescript\nimport { it, ap, pass } from '45';\n\nexport const test = it('applys fn to a value', () =\u003e {\n  const add1 = (x: number) =\u003e x + 1;\n\n  const fn: Assertion\u003c(x: number) =\u003e number\u003e = pass(add1);\n  const value: Assertion\u003cnumber\u003e = pass(1);\n\n  return ap(fn, value); // returns an assertion containing the value 2\n});\n```\n\n#### `chain\u003cA, B\u003e(fn: (a: A) =\u003e Assertion\u003cB\u003e, assertion: Assertion\u003cA\u003e): Assertion\u003cB\u003e`\n\nGiven a function from one value a to Assertion b and an assertion of type a, returns an assertion of type b\nUseful for making many assertions using the values from the previous assertion.\n\n```typescript\nimport { it, chain, equals, map }\n\nexport const test = it('chains many assertions', () =\u003e {\n  const add1 = (x: number) =\u003e x + 1;\n\n  const oneIsOne = equals(1, 1);\n  const isTwo = equals(2); // don't forget, all assertions are curried!\n  const isThree = equals(3);\n\n  return chain(isThree, map(add1, chain(isTwo, map(add1, oneIsOne))));\n})\n```\n\n#### `bimap`\n\nType signature is to long for the header :smile:\n\n```typescript\nbimap\u003cA, B\u003e(\n  failure: (message: string) =\u003e string,\n  success: (a: A) =\u003e B,\n  assertion: Assertion\u003cA\u003e): Assertion\u003cB\u003e;\n```\n\nSimilar to `map` but also allows adjusting error messages.\n\nUseful for creating your own error messages.\n\n```typescript\nimport { bimap, equals, fail, pass } from '45';\n\nexport const test = it('allows adjusting error message', () =\u003e {\n  return bimap(() =\u003e 'Sadly 1 is not correct', fail /* should not pass*/, fail(1));\n});\n```\n\n#### `concat(one: Assertion\u003cA\u003e, two: Assertion\u003cA\u003e): Assertion\u003cA\u003e`\n\nChain together 2 assertions.\n\n```typescript\nimport { concat, pass, it } from '45';\n\nexport const test = it('concatenates', () =\u003e {\n  return concat(pass(1), pass(2)); // passes if and only if both assertions pass.\n});\n```\n\n## Types\n\n```typescript\n// 45 interfaces\nexport interface Test {\n  name: string;\n  timeout: number;\n  run(): Promise\u003cAssertion\u003cany\u003e\u003e;\n  showStatus: boolean;\n}\n\nexport type TestFn =\n  (() =\u003e Assertion\u003cany\u003e) |\n  (() =\u003e Promise\u003cAssertion\u003cany\u003e\u003e) |\n  (() =\u003e Observable\u003cAssertion\u003cany\u003e\u003e);\n\nexport interface TestResult {\n  failures: number;\n  message: string;\n}\n\nexport type Observable\u003cA\u003e =\n  {\n    subscribe(observer: Observer\u003cA\u003e): Subscription;\n  }\n\nexport type Observer\u003cA\u003e =\n  {\n    next(value: A): any;\n    error(err: any): any;\n    complete(): any;\n  }\n\nexport type Subscription =\n  {\n    unsubscribe(): any;\n  }\n\n// 4.5 re-exported interfaces\n\nexport interface Assertion\u003cT\u003e {\n  verify(verification: Verification\u003cT\u003e): void;\n}\n\nexport interface Verification\u003cT\u003e {\n  success(actual: T): any;\n  failure(message: string): any;\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylors%2F45","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftylors%2F45","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylors%2F45/lists"}