{"id":20135537,"url":"https://github.com/raisely/parkes-api-test","last_synced_at":"2026-05-04T18:33:59.723Z","repository":{"id":57319211,"uuid":"108618342","full_name":"raisely/parkes-api-test","owner":"raisely","description":"Concise JSON API tests for your node server","archived":false,"fork":false,"pushed_at":"2019-04-03T09:14:21.000Z","size":116,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-02-15T10:39:07.599Z","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":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/raisely.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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-10-28T04:00:35.000Z","updated_at":"2019-12-20T12:34:01.000Z","dependencies_parsed_at":"2022-08-26T01:10:28.714Z","dependency_job_id":null,"html_url":"https://github.com/raisely/parkes-api-test","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raisely%2Fparkes-api-test","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raisely%2Fparkes-api-test/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raisely%2Fparkes-api-test/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raisely%2Fparkes-api-test/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/raisely","download_url":"https://codeload.github.com/raisely/parkes-api-test/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241582527,"owners_count":19985846,"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-11-13T21:15:21.051Z","updated_at":"2026-05-04T18:33:54.697Z","avatar_url":"https://github.com/raisely.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"Concise JSON API tests for node 7.6.0+\n\nDescribe API (originally parkes-api-test) was built for the Parkes framework,\nbut has no dependencies on parkes or koa so was renamed to express that you can\nuse it to test any node server that is supported by\n[supertest](https://www.npmjs.com/package/supertest).\n\n# Why?\n\nIn environments that are pressed for time (and what development environment isn't?),\nit's easy for tests to get put off.\n\nParkes API Test allows you to quickly build out some e2e tests that at least verify\nthat critical pathways in your API are responding with 200 and the key attributes\nyou'd expect.\n\nThe framework is designed to get tests up in \u003c 5 minutes for most common cases\nso that developers can focus on delivering new features.\n\n# Getting started\n\nParkes API Test requires **node v7.6.0** or higher for ES2015 and async function support.\nThe tests will run in mocha or jest.\n\n```\nnpm install --save-dev parkes-api-test\n```\n\nThen in your `test/api/test.js`\n\n```js\nconst app = require('../../app.js');\nconst describeApi = require('../');\n\nconst person = { name: 'Harvey Milk', state: 'California' };\n\n// Use route modifiers to define things you set on (almost) every route\n// Set custom variables on your routes by prefixing them with $\ndescribeApi.addModifier((route) =\u003e {\n  if (route.$user) route.bearer = getTokenFor(route.$user);\n})\n\ndescribe('my api', () =\u003e {\n  const getServer = describeApi.autorun(app);\n\n  describeApi({ server: getServer, routeModifier: setUser, routes: [\n    // GET /health, expect status == 200\n    { path: '/health' },\n    // POST /people with person body, response should contain { state: 'California' }\n    { method: 'POST', path: '/people', body: person, expect: { state: 'California' } },\n    // GET /people/1 response should contain { name: 'Harvey Milk' }\n    { path: '/people/1', expect: { name: 'Harvey Milk' } },\n    // GET /admin should return 403 error, (logged out) will be appended to test name\n    { path: '/admin', note: '(logged out)', status: 403 },\n    // GET /admin and send auth header, expect status == 200\n    { path: '/admin', bearer: token } },\n    // GET /sitemap send a custom header\n    { path: '/sitemap', headers: { client: 'I am a robot' } },\n\n    // The route modifier added above will set bearer attribute on the route to Bob's token\n    // which will in turn cause describeApi to pass the header Authorization: Bearer \u003cbob's token\u003e\n    { path: '/me', $user: 'Bob' },\n  ] });\n});\n\ndescribe('RESTful api', () =\u003e {\n  describeApi(getServer, '/people', [\n    { }, // GET /people\n    { method: 'POST', body: person }, // POST /people\n    { method: 'PUT', path: '/1' },    // PUT /people/1\n  ],\n  () =\u003e {\n    context('WHEN not authorised', () =\u003e {\n      // NOTE nested calls to describeApi do not require getServer to\n      // be passed in again and use the path prefix from above\n\n      // assert GET /people returns 403 error\n      describeApi([ { status: 403 } ]);\n    });\n\n    context('WHEN person owns pets', () =\u003e {\n      describeApi('/1/pets', () =\u003e {\n        // Expect GET /people/1/pets/1 to be Skippy the Bush Kangaroo\n        [{ path: '/1', expect: { name: 'Skippy', kind: 'Bush Kangaroo' }}],\n      });\n    })\n  });\n});\n```\n\n## What it tests\n`describeApi` will check that the route returns the expected status code (200 by default)\nand that the JSON returned contains at least the properties in the `expect` object.\n\nIf `expect` is not included in the options, then only the status is checked.\n\nBody expectations use [chai-subset](https://github.com/debitoor/chai-subset) to\ndo partial matching on the JSON return value.\n\nIf you're using jest, it's highly recommended you install the optional dependency\n[chai-subset-jest-diff](https://github.com/raisely/chai-subset-jest-diff) so\nthat your test reports have nicely formatted diffs\n\nYou can specify a url prefix as the second parameter to describeApi that will be used for all\npaths.\n\n## Defining routes to test\n`describeApi` takes an array of routes to test. Each route is defined by an object\nwith the following keys.\n\nAll are optional. An empty object will result in a test that calls GET / expects a\nreturn status of 200.\n\n| key | default | description |\n| --- | --- | ---- |\n| method | GET | The HTTP method |\n| path* | '' | Path to request |\n| name | \\`${method} ${path} (${note})\\` | Specify if you want to override the route name |\n| status | 200 | Expected status code from the server |\n| note | | A helpful note to differentiate the route from others |\n| body* | | Body to send with the request |\n| expect* | | Partial object to match JSON body against (can also be text) |\n| headers* | | An object of headers to pass to the request |\n| bearer | | Token to set in the Authorization: Bearer header |\n| describe | | Callback to execute within the context of the route's describe block |\n\nOptions marked * can be a (async) function in which case the return value of the function\nwill be used. The functions are evaluated during test execution.\n\nTests by the same name may be grouped within the same describe statement by your\ntest runner, this could lead to unexpected results, so if you are testing the same\nroute more than once, you should use `note` to differentiate the tests.\n\n## Payloads always in `data`\nExpect and body by default assume that the API nests the objects within a data attribute.\nIf you want to specify these objects without the data wrapper, use \\_body and \\_expect.\n\n```js\nroutes = [{ '/', expect: person }];\n\n// Expects GET / to return\n{\n  data: {\n    name: 'Harvey Milk',\n    state: 'California'\n  }\n}\n\nroutes = [{ '/', _expect: person }];\n// Expects GET / to return\n{\n  name: 'Harvey Milk',\n  state: 'California'\n}\n\n```\n\n## Passing the server in via a function\nBecause tests may want to spin up the server within before and after blocks\ndescribeApi takes a function that returns the server.\n\nWhen a route is executed that function will be called to retrieve the server.\n\nTo simplify this setup, describeApi provides a helper function `autorun`\nwhich will run the necessary `before` and `after` blocks to start up the\nserver and close it again when done.\n\nAutorun expects an `app` that behaves like a koa app (returns a HttpServer from\n`app.listen()`)\n\n```js\nconst app = require('koa')();\n\napp.use(myMiddleware);\n\nconst getServer = describeApi.autorun(app);\n\ndescribeApi(getServer, ...)\n```\n\n## Extending the describe blocks\nIf you need more tests or hooks in the describe block for a particular test, use the describe\ncallback.\n\n```js\ndescribe('my api', () =\u003e {\n  describeApi(server, [\n    {\n      path: '/get_gookie',\n      describe: () =\u003e {\n        it('returns a cookie', (response) =\u003e {\n          expect(response.headers.cookies).to.be.ok;\n        });\n      }\n    },\n  });\n});\n```\n\n### afterRoute and beforeRoute\nTo avoid confusion due to every route running only once in it's describe block,\nparkes-api-test defines `beforeRoute` and `afterRoute` which are\naliases for before(All) and after(All).\n\n### `afterRoute` and `it` inside a route's describe block\n\nInside route describes, `afterRoute` and `it` functions are overridden to\npass additional arguments to the callback should you need them.\n\n`it('', (response, resolvedRoute) =\u003e {})`\n`afterAll((response, resolvedRoute) =\u003e {})`\n\n`response` is the supertest response object\n\n`resolvedRoute` is a copy of the route object with all dynamic attributes and any route modifiers resolved\n\n## Nesting blocks\nBecause describe blocks are used, you can nest them as you would expect.\n\nYou can also nest within a describe block for your router\n\nNested describeApi's inherit the `getServer` method and path from the above scope\n\n```js\ndescribe('my api', () =\u003e {\n  describeApi(server, '/people', [\n    { path: '/1' },\n  ],\n  () =\u003e {\n    describe('WHEN they have pets', () =\u003e {\n      describeApi('/1/pets', [\n        { path: '/1' }\n      ]);\n    });\n  });\n});\n```\n\n## Under the hood\n\nThe test runner sets up a `describe` block for each route.\n\nEach route is called during the **beforeAll** phase of the test. This is different from\nmost test patterns, but this has the benefit that in order to assert things about the\nresult of a HTTP request you only need to make that request once.\n\nEach route will have one `it` block for asserting the return status and body content of\nthe route.\n\nIf you define `describe` then all the `it` blocks logically sit within the describe\nblock for that route.\n\n```js\n\n// Example\ndescribe('my api', () =\u003e {\n  describeApi(getServer, '/user', [\n    { expect: expectedJson, describe: () =\u003e {\n      it('passes response to it block', (response) =\u003e {\n        expect(response.status).to.be.ok;\n      });\n    } }\n  ]);\n});\n\n// Is the equivalent of\ndescribe('my api', () =\u003e {\n  describe('GET /user', () =\u003e {\n    let response;\n\n    beforeAll(() =\u003e {\n      response = await getServer().get('/');\n    })\n\n    it('status 200', () =\u003e {\n      expect(response.status).to.eq(200);\n    });\n    it('body is correct', () =\u003e {\n      expect(response.body).to.containSubset(expectedJson);\n    });\n    it('passes response to it block', (response) =\u003e {\n      expect(response.status).to.be.ok;\n    });\n  });\n})\n```\n\n## Known Issues\nUsing `mocha -w` is currently broken as the before hooks do not run on subsequent\nruns.\n(The same problem does not occur when using Jest)\n\n# License\n\n© 2017 Agency Ventures\n\nLicensed under the JWL license.  See [`LICENSE.md`](https://github.com/raisely/parkes-api-test/blob/master/LICENSE.md) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraisely%2Fparkes-api-test","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fraisely%2Fparkes-api-test","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraisely%2Fparkes-api-test/lists"}