{"id":13671116,"url":"https://github.com/paralleldrive/riteway","last_synced_at":"2025-05-14T18:05:24.559Z","repository":{"id":23983805,"uuid":"100315392","full_name":"paralleldrive/riteway","owner":"paralleldrive","description":"Simple, readable, helpful unit tests.","archived":false,"fork":false,"pushed_at":"2025-05-14T06:40:22.000Z","size":4423,"stargazers_count":1153,"open_issues_count":20,"forks_count":35,"subscribers_count":21,"default_branch":"master","last_synced_at":"2025-05-14T07:45:59.899Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/paralleldrive.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-08-14T22:48:06.000Z","updated_at":"2025-03-27T05:54:06.000Z","dependencies_parsed_at":"2023-10-11T00:41:27.305Z","dependency_job_id":"d729fe70-97d1-4f46-b786-f6cb5f36ca96","html_url":"https://github.com/paralleldrive/riteway","commit_stats":{"total_commits":400,"total_committers":28,"mean_commits":"14.285714285714286","dds":0.515,"last_synced_commit":"3b57e23deb51963f5b31c18b52c65b04a0ab4b53"},"previous_names":["ericelliott/riteway"],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paralleldrive%2Friteway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paralleldrive%2Friteway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paralleldrive%2Friteway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paralleldrive%2Friteway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paralleldrive","download_url":"https://codeload.github.com/paralleldrive/riteway/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254198514,"owners_count":22030965,"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":[],"created_at":"2024-08-02T09:00:59.661Z","updated_at":"2025-05-14T18:05:19.543Z","avatar_url":"https://github.com/paralleldrive.png","language":"JavaScript","readme":"# Riteway\n\nSimple, readable, helpful unit tests.\n\n* **R**eadable\n* **I**solated/**I**ntegrated\n* **T**horough\n* **E**xplicit\n\nRiteway forces you to write **R**eadable, **I**solated, and **E**xplicit tests, because that's the only way you can use the API. It also makes it easier to be thorough by making test assertions so simple that you'll want to write more of them.\n\nThere are [5 questions every unit test must answer](https://medium.com/javascript-scene/what-every-unit-test-needs-f6cd34d9836d). Riteway forces you to answer them.\n\n1. What is the unit under test (module, function, class, whatever)?\n2. What should it do? (Prose description)\n3. What was the actual output?\n4. What was the expected output?\n5. How do you reproduce the failure?\n\n\n## Installing\n\n```shell\nnpm install --save-dev riteway\n```\n\nThen add an npm command in your package.json:\n\n```json\n\"test\": \"riteway test/**/*-test.js\",\n```\n\nNow you can run your tests with `npm test`. Riteway also supports full TAPE-compatible usage syntax, so you can have an advanced entry that looks like:\n\n```json\n\"test\": \"nyc riteway test/**/*-rt.js | tap-nirvana\",\n```\n\nIn this case, we're using [nyc](https://www.npmjs.com/package/nyc), which generates test coverage reports. The output is piped through an advanced TAP formatter, [tap-nirvana](https://www.npmjs.com/package/tap-nirvana) that adds color coding, source line identification and advanced diff capabilities.\n\n### Troubleshooting\n\nIf you get an error like:\n\n```shell\nSyntaxError: Unexpected identifier\n    at new Script (vm.js:79:7)\n    at createScript (vm.js:251:10)\n    at Object.runInThisContext (vm.js:303:10)\n...\n```\n\nThe problem is likely that you need a `.babelrc` configured with support for esm (standard JavaScript modules) and/or React. If you need React support, that might look something like:\n\n```json\n{\n  \"presets\": [\n    [\n      \"@babel/preset-env\",\n      {\n        \"targets\": [\n          \"last 2 versions\",\n          \"safari \u003e= 7\"\n        ]\n      }\n    ],\n    \"@babel/preset-react\"\n  ],\n  \"plugins\": [\n    [\n      \"@babel/plugin-transform-runtime\",\n      { \"corejs\": 2 }\n    ]\n  ]\n}\n```\n\nTo install babel devDependencies:\n\n```shell\nnpm install --save-dev @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/register @babel/runtime-corejs2\n```\n\nAnd if you're using react:\n\n```shell\nnpm install --save-dev @babel/preset-react\n```\n\nYou can then update your test script in `package.json` to use babel:\n\n```json\n\"test\": \"node -r @babel/register source/test\"\n```\n\nIf you structure your folders by type like this:\n\n```shell\n├──todos\n│  ├── component\n│  ├── reducer\n│  └── test\n└──user\n   ├── component\n   ├── reducer\n   └── test\n```\n\nUpdate your test script to find all files with your custom ending:\n\n```json\n\"test\": \"riteway -r @babel/register 'src/**/*.test.js' | tap-nirvana\",\n```\n\n### Usage with ESM Modules\n\nIf you want to use ESM modules instead of compiling, you'll need to import from the esm folder:\n\n```js\nimport { describe } from 'riteway/esm/riteway.js';\nimport { match } from 'riteway/esm/match.js';\n\n// Note: If you're using a compiler for JSX, you might want to handle\n// ESM modules with it, too, so you shouldn't need the ESM modules, but\n// if you want to use them anyway:\nimport { render } from 'riteway/esm/render.js';\n```\n\nThe script to run your tests should be run using `node` instead of `riteway`, like this:\n\n```json\n\"test\": \"node test/index-test.js\",\n```\n\nSince the tests are being run using `node`, it will not be possible to use globs to select the test files. You're encouraged to create a single entry test file which imports all test files from your project.\n\n### Usage with SWC\n\nSWC is a fast, Rust based compiler that is [the new default compiler in Next.js 12+](https://nextjs.org/blog/next-12#faster-builds-and-fast-refresh-with-rust-compiler).\n\nIf you'd like to use it, there are some additional configuration steps:\n\n1. Configure SWC to recognize React and JSX.\n2. Configure your project to use [absolute import paths](https://nextjs.org/docs/advanced-features/module-path-aliases).\n3. Configure SWC to recognize `.module.css` files or `.css` files in your Next.js project.\n4. Configure SWC to run `styled-jsx` plugins, if you use it.\n\nHere is how you can compile your code with SWC and run Riteway tests.\n\nInstall [`@swc/core`](https://swc.rs/docs/getting-started) and [`@swc/register`](https://github.com/swc-project/register):\n\n```\nnpm install --save-dev @swc/core @swc/register\n```\n\nor\n\n```\nyarn add --dev @swc/core @swc/register\n```\n\nAdd a `\"test\"` script to your `package.json`:\n\n```json\n\"test\": \"node -r @swc/register src/test.js\",\n```\n\nCreate an `.swcrc` file with the options you need. Hint: Try the [SWC Playground](https://swc.rs/playground) for help generating valid SWC configurations. Example with css modules, absolute path, and React support:\n\n```json\n{\n  \"jsc\": {\n    \"baseUrl\": \"./src\",\n    \"paths\": {\n      \"*.css\": [\"utils/identity-object-proxy.js\"],\n      \"utils/*\": [\"utils/*\"]\n    },\n    \"parser\": {\n      \"jsx\": true,\n      \"syntax\": \"ecmascript\"\n    },\n    \"transform\": {\n      \"react\": {\n        \"runtime\": \"automatic\"\n      }\n    },\n    \"experimental\": {\n      \"plugins\": [\n        [\"@swc/plugin-styled-jsx\", {}]\n      ]\n    }\n  },\n  \"module\": {\n    \"type\": \"commonjs\"\n  }\n}\n```\n\nThe `\"baseUrl\"` setting combined with `\"utils/*\": [\"utils/*\"]` is an example if you're using an absolute import from `src/utils`, e.g. `utils/pipe`. You'll need to add this for every folder for which you're using absolute imports.\n\nThe `\"parser\"` and `\"transform\"` settings tell SWC how to handle JSX and [files that use React without importing it](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html).\n\nThe `\"module\"` settings allows you to use modern syntax like `import`.\n\n`\"*.css\": [\"utils/identity-object-proxy.js\"],` tells SWC to replace all absolute CSS imports with an identity object proxy. (**Note:** relative imports like `import './styles.css` will NOT be replaced, and you need to convert them to absolute imports.) An identity object proxy is an object that returns each key stringified when accessed. For example `obj.foo` returns `\"foo\"`.\n\nYou will need to create the identity object proxy yourself. Here is an example:\n\n```js\nconst identityObjectProxy = new Proxy(\n  {},\n  {\n    get: function getter(target, key) {\n      if (key === '__esModule') {\n        return false;\n      }\n\n      return key;\n    },\n  },\n);\n\nexport default identityObjectProxy;\n```\n\n\n## Example Usage\n\n```js\nimport { describe, Try } from 'riteway';\n\n// a function to test\nconst sum = (...args) =\u003e {\n  if (args.some(v =\u003e Number.isNaN(v))) throw new TypeError('NaN');\n  return args.reduce((acc, n) =\u003e acc + n, 0);\n};\n\ndescribe('sum()', async assert =\u003e {\n  const should = 'return the correct sum';\n\n  assert({\n    given: 'no arguments',\n    should: 'return 0',\n    actual: sum(),\n    expected: 0\n  });\n\n  assert({\n    given: 'zero',\n    should,\n    actual: sum(2, 0),\n    expected: 2\n  });\n\n  assert({\n    given: 'negative numbers',\n    should,\n    actual: sum(1, -4),\n    expected: -3\n  });\n\n  assert({\n    given: 'NaN',\n    should: 'throw',\n    actual: Try(sum, 1, NaN).toString(),\n    expected: 'TypeError: NaN'\n  });  \n});\n```\n\n### Testing React Components\n\n```js\nimport React from 'react';\nimport render from 'riteway/render-component';\nimport { describe } from 'riteway';\n\ndescribe('renderComponent', async assert =\u003e {\n  const $ = render(\u003cdiv className=\"foo\"\u003etesting\u003c/div\u003e);\n\n  assert({\n    given: 'some jsx',\n    should: 'render markup',\n    actual: $('.foo').html().trim(),\n    expected: 'testing'\n  });\n});\n```\n\nRiteway makes it easier than ever to test pure React components using the `riteway/render-component` module. A pure component is a component which, given the same inputs, always renders the same output.\n\nI don't recommend unit testing stateful components, or components with side-effects. Write functional tests for those, instead, because you'll need tests which describe the complete end-to-end flow, from user input, to back-end-services, and back to the UI. Those tests frequently duplicate any testing effort you would spend unit-testing stateful UI behaviors. You'd need to do a lot of mocking to properly unit test those kinds of components anyway, and that mocking may cover up problems with too much coupling in your component. See [\"Mocking is a Code Smell\"](https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a) for details.\n\nA great alternative is to encapsulate side-effects and state management in container components, and then pass state into pure components as props. Unit test the pure components and use functional tests to ensure that the complete UX flow works in real browsers from the user's perspective.\n\n#### Isolating React Unit Tests\n\nWhen you [unit test React components](https://medium.com/javascript-scene/unit-testing-react-components-aeda9a44aae2) you frequently have to render your components many times. Often, you want different props for some tests.\n\nRiteway makes it easy to isolate your tests while keeping them readable by using [factory functions](https://link.medium.com/WxHPhCc3OV) in conjunction with [block scope](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block).\n\n```js\nimport ClickCounter from '../click-counter/click-counter-component';\n\ndescribe('ClickCounter component', async assert =\u003e {\n  const createCounter = clickCount =\u003e\n    render(\u003cClickCounter clicks={ clickCount } /\u003e)\n  ;\n\n  {\n    const count = 3;\n    const $ = createCounter(count);\n    assert({\n      given: 'a click count',\n      should: 'render the correct number of clicks.',\n      actual: parseInt($('.clicks-count').html().trim(), 10),\n      expected: count\n    });\n  }\n\n  {\n    const count = 5;\n    const $ = createCounter(count);\n    assert({\n      given: 'a click count',\n      should: 'render the correct number of clicks.',\n      actual: parseInt($('.clicks-count').html().trim(), 10),\n      expected: count\n    });\n  }\n});\n```\n\n## Output\n\nRiteway produces standard TAP output, so it's easy to integrate with just about any test formatter and reporting tool. (TAP is a well established standard with hundreds (thousands?) of integrations).\n\n```shell\nTAP version 13\n# sum()\nok 1 Given no arguments: should return 0\nok 2 Given zero: should return the correct sum\nok 3 Given negative numbers: should return the correct sum\nok 4 Given NaN: should throw\n\n1..4\n# tests 4\n# pass  4\n\n# ok\n```\n\nPrefer colorful output? No problem. The standard TAP output has you covered. You can run it through any TAP formatter you like:\n\n```shell\nnpm install -g tap-color\nnpm test | tap-color\n```\n\n![Colorized output](docs/tap-color-screenshot.png)\n\n\n## API\n\n### describe\n\n```js\ndescribe = (unit: String, cb: TestFunction) =\u003e Void\n```\n\nDescribe takes a prose description of the unit under test (function, module, whatever), and a callback function (`cb: TestFunction`). The callback function should be an [async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) so that the test can automatically complete when it reaches the end. Riteway assumes that all tests are asynchronous. Async functions automatically return a promise in JavaScript, so Riteway knows when to end each test.\n\n### describe.only\n\n```js\ndescribe.only = (unit: String, cb: TestFunction) =\u003e Void\n```\n\nLike Describe, but don't run any other tests in the test suite.  See [test.only](https://github.com/substack/tape#testonlyname-cb)\n\n### describe.skip\n\n```js\ndescribe.skip = (unit: String, cb: TestFunction) =\u003e Void\n```\n\nSkip running this test. See [test.skip](https://github.com/substack/tape#testskipname-cb)\n\n### TestFunction\n\n```js\nTestFunction = assert =\u003e Promise\u003cvoid\u003e\n```\n\nThe `TestFunction` is a user-defined function which takes `assert()` and must return a promise. If you supply an async function, it will return a promise automatically. If you don't, you'll need to explicitly return a promise.\n\nFailure to resolve the `TestFunction` promise will cause an error telling you that your test exited without ending. Usually, the fix is to add `async` to your `TestFunction` signature, e.g.:\n\n```js\ndescribe('sum()', async assert =\u003e {\n  /* test goes here */\n});\n```\n\n\n### assert\n\n```js\nassert = ({\n  given = Any,\n  should = '',\n  actual: Any,\n  expected: Any\n} = {}) =\u003e Void, throws\n```\n\nThe `assert` function is the function you call to make your assertions. It takes prose descriptions for `given` and `should` (which should be strings), and invokes the test harness to evaluate the pass/fail status of the test. Unless you're using a custom test harness, assertion failures will cause a test failure report and an error exit status.\n\nNote that `assert` uses [a deep equality check](https://github.com/substack/node-deep-equal) to compare the actual and expected values. Rarely, you may need another kind of check. In those cases, pass a JavaScript expression for the `actual` value.\n\n### createStream\n\n```js\ncreateStream = ({ objectMode: Boolean }) =\u003e NodeStream\n```\n\nCreate a stream of output, bypassing the default output stream that writes messages to `console.log()`. By default the stream will be a text stream of TAP output, but you can get an object stream instead by setting `opts.objectMode` to `true`.\n\n```js\nimport { describe, createStream } from 'riteway';\n\ncreateStream({ objectMode: true }).on('data', function (row) {\n    console.log(JSON.stringify(row))\n});\n\ndescribe('foo', async assert =\u003e {\n  /* your tests here */\n});\n```\n\n### countKeys\n\nGiven an object, return a count of the object's own properties.\n\n```js\ncountKeys = (Object) =\u003e Number\n```\n\nThis function can be handy when you're adding new state to an object keyed by ID, and you want to ensure that the correct number of keys were added to the object.\n\n\n## Render Component\n\nFirst, import `render` from `riteway/render-component`:\n\n```js\nimport render from 'riteway/render-component';\n```\n\n```js\nrender = (jsx) =\u003e CheerioObject\n```\n\nTake a JSX object and return a [Cheerio object](https://cheerio.js.org/), a partial implementation of the jQuery core API which makes selecting from your rendered JSX markup just like selecting with jQuery or the `querySelectorAll` API.\n\n### Example\n\n```js\ndescribe('MyComponent', async assert =\u003e {\n  const $ = render(\u003cMyComponent /\u003e);\n\n  assert({\n    given: 'no params',\n    should: 'render something with the my-component class',\n    actual: $('.my-component').length,\n    expected: 1\n  });\n});\n```\n\n\n## Match\n\nFirst, import `match` from `riteway/match`:\n\n```js\nimport match from 'riteway/match';\n```\n\n```js\nmatch = text =\u003e pattern =\u003e String\n```\n\nTake some text to search and return a function which takes a pattern and returns the matched text, if found, or an empty string. The pattern can be a string or regular expression.\n\n### Example\n\nImagine you have a React component you need to test. The component takes some text and renders it in some div contents. You need to make sure that the passed text is getting rendered.\n\n```js\nconst MyComponent = ({text}) =\u003e \u003cdiv className=\"contents\"\u003e{text}\u003c/div\u003e;\n```\n\nYou can use match to create a new function that will test to see if your search\ntext contains anything matching the pattern you passed in. Writing tests this way\nallows you to see clear expected and actual values, so you can expect the specific\ntext you're expecting to find:\n\n```js\ndescribe('MyComponent', async assert =\u003e {\n  const text = 'Test for whatever you like!';\n  const $ = render(\u003cMyComponent text={ text }/\u003e);\n  const contains = match($('.contents').html());\n\n  assert({\n    given: 'some text to display',\n    should: 'render the text.',\n    actual: contains(text),\n    expected: text\n  });\n});\n```\n\n## Vitest\n\n[Vitest](https://vitest.dev/guide/) is a [Vite](https://vitejs.dev/) plugin through which you can run Riteway tests. It's a great way to get started with Riteway, because it's easy to set up and it's fast.\n\n### Installing\n\nFirst you will need to install Vitest. You will also need to install Riteway into your project if you have not already done so. You can use any package manager you like:\n\n```shell\npnpm add --save-dev vitest\nnpm install --save-dev vitest\nyarn add --save-dev vitest\n```\n\n### Usage\n\nFirst, import `assert` from `riteway/vitest` and `describe` from `vitest`:\n\n```ts\nimport { assert } from 'riteway/vitest';\nimport { describe } from \"vitest\";\n```\n\nThen, as simple as that, you should be able to use the Vitest runner to test. You can trigger the Vitest directly or add a script to your package.json. Running `npm vitest` would do the trick to see a basic test setup. See [here](https://vitest.dev/config/) for additional details on setting up a Vitest configuration.\n\n\n```ts\n// a function to test\nconst sum = (...args) =\u003e {\n  if (args.some(v =\u003e Number.isNaN(v))) throw new TypeError('NaN');\n  return args.reduce((acc, n) =\u003e acc + n, 0);\n};\n\ndescribe('sum()', () =\u003e {\n  const should = 'return the correct sum';\n\n  assert({\n    given: 'no arguments',\n    should: 'return 0',\n    actual: sum(),\n    expected: 0\n  });\n\n  assert({\n    given: 'two numbers',\n    should: 'return the correct sum',\n    actual: sum(2, 0),\n    expected: 2\n  });\n});\n```","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparalleldrive%2Friteway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fparalleldrive%2Friteway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparalleldrive%2Friteway/lists"}