{"id":31844481,"url":"https://github.com/abelosorio/chai-express","last_synced_at":"2026-04-15T05:32:22.594Z","repository":{"id":57196551,"uuid":"130387005","full_name":"abelosorio/chai-express","owner":"abelosorio","description":"Chai's assertions for Express","archived":false,"fork":false,"pushed_at":"2018-09-18T14:33:30.000Z","size":55,"stargazers_count":2,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-19T16:52:52.382Z","etag":null,"topics":["api","chai","express","nodejs","router","testing"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/chai-express","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/abelosorio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-20T16:22:02.000Z","updated_at":"2018-09-19T15:18:53.000Z","dependencies_parsed_at":"2022-09-16T20:13:14.539Z","dependency_job_id":null,"html_url":"https://github.com/abelosorio/chai-express","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/abelosorio/chai-express","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abelosorio%2Fchai-express","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abelosorio%2Fchai-express/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abelosorio%2Fchai-express/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abelosorio%2Fchai-express/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/abelosorio","download_url":"https://codeload.github.com/abelosorio/chai-express/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abelosorio%2Fchai-express/sbom","scorecard":{"id":160488,"data":{"date":"2025-08-11","repo":{"name":"github.com/abelosorio/chai-express","commit":"df79a1ef040cf3c8b005f51d7ffd694fbe18eab9"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.4,"checks":[{"name":"Code-Review","score":1,"reason":"Found 2/20 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 13 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"57 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-cwfw-4gq5-mrqx","Warn: Project is vulnerable to: GHSA-g95f-p29q-9xw4","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-c6rq-rjc2-86v2","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-9vvw-cc9w-f27h","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-hr2v-3952-633q","Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc","Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx","Warn: Project is vulnerable to: GHSA-qrmc-fj45-qfc2","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-8r6j-v8pm-fqw3","Warn: Project is vulnerable to: MAL-2023-462","Warn: Project is vulnerable to: GHSA-xf7w-r453-m56c","Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5","Warn: Project is vulnerable to: GHSA-44pw-h2cw-w3vq","Warn: Project is vulnerable to: GHSA-jp4x-w63m-7wgm","Warn: Project is vulnerable to: GHSA-c429-5p7v-vgjp","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-675m-85rw-j3w4","Warn: Project is vulnerable to: GHSA-4xc9-xhrj-v574","Warn: Project is vulnerable to: GHSA-x5rq-j2xg-h7qm","Warn: Project is vulnerable to: GHSA-jf85-cpcp-j695","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-g6ww-v8xp-vmwg","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-6g33-f262-xjp4","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg","Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p","Warn: Project is vulnerable to: GHSA-2m39-62fm-q8r3","Warn: Project is vulnerable to: GHSA-mf6x-7mm4-x2g7","Warn: Project is vulnerable to: GHSA-j44m-qm6p-hp7m","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-g7q5-pjjr-gqvp","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-16T13:16:12.212Z","repository_id":57196551,"created_at":"2025-08-16T13:16:12.212Z","updated_at":"2025-08-16T13:16:12.212Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31828531,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T18:05:02.291Z","status":"online","status_checked_at":"2026-04-15T02:00:06.175Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["api","chai","express","nodejs","router","testing"],"created_at":"2025-10-12T07:29:43.691Z","updated_at":"2026-04-15T05:32:22.582Z","avatar_url":"https://github.com/abelosorio.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# chai-express\n[![Build Status](https://travis-ci.org/abelosorio/chai-express.svg?branch=master)](https://travis-ci.org/abelosorio/chai-express) [![NPM version](https://img.shields.io/npm/v/chai-express.svg)](https://www.npmjs.com/package/chai-express)\n\nIt provides useful [Chai's assertions](https://www.npmjs.com/package/chai)  to use in [Express' routes](https://www.npmjs.com/package/express).\n\n## Installation\n\n```\nnpm install --save-dev chai-express\nnpm install --save-dev sinon sinon-chai # if you want to use  my approach for simulateRouteDispatch.\n```\n\n## How to use\n\n### src/app.js (minimal configuration for tests to work)\n\n```javascript\nimport express from 'express';\n\nconst app = express();\nconst router = express.Router();\n\napp.use(router);\n\nrequire('./routes/user')(router);\n\nexport { router, app as default };\n```\n\n### src/controllers/user.js\n```javascript\nimport User from '../models/user';\n\nexport function getAll(req, res) {\n  res.send(User.findAll());\n}\n```\n\n### src/routes/user.js\n```javascript\nimport * as UserController from '../controllers/user';\n\nexport default function (router) {\n  router.get('/users', UserController.getAll);\n}\n```\n\n### test/routes/user.js\n\n```javascript\nimport chai from 'chai';\nimport chaiExpress, { simulateRouteDispatch } from 'chai-express';\nimport sinon from 'sinon';\nimport sinonChai from 'sinon-chai';\n\nimport { router } from '../src/app';\nimport * as UserController from '../src/controllers/user';\n\nchai.use(chaiExpress);\nchai.use(sinonChai);\n\ndescribe('routes', () =\u003e {\n  describe('GET /users', () =\u003e {\n    it('should define it', () =\u003e {\n      expect(router).to.have.route(/^\\/users_1\\/?$/i);\n    });\n\n    it('should call to the right controller', () =\u003e {\n      sinon.spy(UserController, 'getAll');\n\n      simulateRouteDispatch(router, 'get', /^\\/users_1\\/?$/i);\n\n      expect(UserController.getAll).to.be.calledOnce;\n\n      UserController.getAll.restore();\n    });\n  });\n});\n```\n\n## Motivation\n\nI'll write this from my point of view using what I learned so far and trying to use as many references as I can. Feel free to put your comments or thoughts in an [issue](https://github.com/abelosorio/chai-express/issues) if you want.\n\n### The problem I found\n\nIn every single article or post I've read related to testing routes I found the same problems:\n\n1.  Those are not unit tests.\n2.  Those tests are not testing only the routes, but also the controllers.\n\nAs [J.B. Rainsberger said](https://vimeo.com/80533536) it's almost impossible to write **unit** tests. Instead we try to isolate functionality and test it in an isolated and reduced scope. Hence Rainsberger prefers to use the term **Isolated tests**. And I do prefer it too.\n\nBefore moving on I should mention another thing I noticed in these articles. They do not divide controllers from routes. They show source code like this:\n\n`route/users.js`:\n```javascript\n// ...\nrouter.get('/users/all', (req, res) =\u003e res.send(User.findAll()));\n// ...\n```\n\nIn that code the route and the controller are defined in a single line. There is no clearly distinction between controller and route's responsibility.\n\nAgain, this is not what I learned. Christopher Okhravi is very clear [in this video](https://www.youtube.com/watch?v=AEnePs2Evg0) about what *Single Responsibility* is: a single reason to change.\n\nIn the code above there are multiple reasons to change. Just to mention some:\n\n1.  The route could change from `/users/all` to just `/users`.\n2.  We could expect a JSON output instead of a plain stringified object.\n3.  The User model's method `findAll` could change to `getAll`, or change its arguments.\n\n### A possible solution\n\nThe first refactor we should perform in this code is to lift up the controller's function to an another file:\n\n`controllers/user.js`:\n```javascript\n// ...\nexport function getAll(req, res) {\n  return res.send(User.findAll();\n}\n```\n\n`routes/user.js`:\n```javascript\nimport * as UserController from '../controllers/user';\n\n//...\nrouter.get('/users/all', UserController.getAll);\n//...\n```\n\nA lot better, isn't it?\n\nSo, what should we test now? Remember, we are testing our API **router**.\n\n## What should you test in an API router?\n\nThis checklist is meant to suit in a well modularized API framework. This means that you will need *at least* to move your route handlers to controllers as shown [here](#a-possible-solution).\n\nSo, the checklist:\n\n- Check if the route is defined throught regular expression.\n- Check if the a route instance is accepted by your API router.\n- Check if the route accepts the right parameters.\n- Check if the route calls to the right controller's method.\n- Check if the route has the right  ACLs.\n\nYou will notice that we are not testing the endpoints. In this point we don't care if they work properly or respond as expected.\n\nWhy? Express has (or should have) its own tests. So, we delegate the **responsibility** of responding to a route to Express. Thus, the router is not responsible for calling to any model or perform no other action but **calling a controller's function**. In that way, the responsible for calling to models and perform actions would be the **controller** and, of course, we have another `test/controllers` directoy testing all our controllers.\n\n## Usage examples.\nBelow you find some possible examples using ***expect*** assertion style.\n\n```javascript\n  it('should assert correctly that a route exists', () =\u003e {\n    expect(router).to.have.route('get', /^\\/users\\/?$/i);\n  });\n\n  it('should assert correctly that a route doesn\\'t exists', () =\u003e {\n    expect(router).to.not.have.route('get', /^\\/users_1\\/?$/i);\n    expect(router).to.not.have.route('put', /^\\/users\\/?$/i);\n  });\n\n  it('should assert router accepts provided route', () =\u003e {\n    expect(router).to.acceptRoute('get', '/api/v1/lookup-by-id/1');\n  });\n\n  it('should assert router do not accepts provided route', () =\u003e {\n    expect(router).to.not.acceptRoute('get', '/api/v1/lookup-by-id/');\n    expect(router).to.not.acceptRoute('put', '/api/v1/lookup-by-id/1');\n  });\n```\n\n## Changelog\n\n[CHANGELOG](CHANGELOG.md)\n\n## Contribute\n\nPlease do it! If you have any idea please [create an issue](https://github.com/abelosorio/chai-express/issues) and we can discuss about it.\n\n\n## Issues\n[https://github.com/abelosorio/chai-express/issues](https://github.com/abelosorio/chai-express/issues)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabelosorio%2Fchai-express","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabelosorio%2Fchai-express","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabelosorio%2Fchai-express/lists"}