├── .codeclimate.yml ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── conf.json ├── dist ├── chakram.js ├── chakram.js.map ├── chakram.min.js └── chakram.min.js.map ├── examples ├── dweet.js └── randomuser.js ├── lib ├── assertions │ ├── arrayIncluding.js │ ├── arrayLength.js │ ├── compression.js │ ├── comprise.js │ ├── cookie.js │ ├── header.js │ ├── index.js │ ├── json.js │ ├── responsetime.js │ ├── schema.js │ └── statuscode.js ├── chakram.js ├── debug.js ├── methods.js ├── plugins.js └── utils │ └── objectPath.js ├── package.json └── test ├── assertions ├── arrayIncluding.js ├── arrayLength.js ├── compression.js ├── cookie.js ├── header.js ├── json.js ├── responsetime.js ├── schema.js └── statuscode.js ├── bootstrap.js ├── core ├── base.js ├── debug.js ├── documentation-examples.js ├── methods.js ├── plugins.js └── warnings.js ├── index.html └── mocha.opts /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | eslint: 3 | enabled: true 4 | config: 5 | extensions: 6 | - .js 7 | ratings: 8 | paths: 9 | - "**.js" 10 | exclude_paths: 11 | - dist/* 12 | - coverage/* 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | ecmaFeatures: 2 | modules: true 3 | 4 | env: 5 | amd: true 6 | node: true 7 | browser: true 8 | 9 | # http://eslint.org/docs/rules/ 10 | rules: 11 | # Possible Errors 12 | comma-dangle: [2, never] 13 | no-cond-assign: 2 14 | no-console: 0 15 | no-constant-condition: 2 16 | no-control-regex: 2 17 | no-debugger: 2 18 | no-dupe-args: 2 19 | no-dupe-keys: 2 20 | no-duplicate-case: 2 21 | no-empty: 2 22 | no-empty-character-class: 2 23 | no-ex-assign: 2 24 | no-extra-boolean-cast: 2 25 | no-extra-parens: 0 26 | no-extra-semi: 2 27 | no-func-assign: 2 28 | no-inner-declarations: [2, functions] 29 | no-invalid-regexp: 2 30 | no-irregular-whitespace: 2 31 | no-negated-in-lhs: 2 32 | no-obj-calls: 2 33 | no-regex-spaces: 2 34 | no-sparse-arrays: 2 35 | no-unexpected-multiline: 2 36 | no-unreachable: 2 37 | use-isnan: 2 38 | valid-jsdoc: 0 39 | valid-typeof: 2 40 | 41 | # Best Practices 42 | accessor-pairs: 2 43 | block-scoped-var: 0 44 | complexity: [2, 6] 45 | consistent-return: 0 46 | curly: 0 47 | default-case: 0 48 | dot-location: 0 49 | dot-notation: 0 50 | eqeqeq: 2 51 | guard-for-in: 0 52 | no-alert: 2 53 | no-caller: 2 54 | no-case-declarations: 2 55 | no-div-regex: 2 56 | no-else-return: 0 57 | no-empty-label: 2 58 | no-empty-pattern: 2 59 | no-eq-null: 2 60 | no-eval: 2 61 | no-extend-native: 2 62 | no-extra-bind: 2 63 | no-fallthrough: 2 64 | no-floating-decimal: 0 65 | no-implicit-coercion: 0 66 | no-implied-eval: 2 67 | no-invalid-this: 0 68 | no-iterator: 2 69 | no-labels: 0 70 | no-lone-blocks: 2 71 | no-loop-func: 2 72 | no-magic-number: 0 73 | no-multi-spaces: 0 74 | no-multi-str: 0 75 | no-native-reassign: 2 76 | no-new-func: 2 77 | no-new-wrappers: 2 78 | no-new: 2 79 | no-octal-escape: 2 80 | no-octal: 2 81 | no-proto: 2 82 | no-redeclare: 2 83 | no-return-assign: 2 84 | no-script-url: 2 85 | no-self-compare: 2 86 | no-sequences: 0 87 | no-throw-literal: 0 88 | no-unused-expressions: 0 89 | no-useless-call: 2 90 | no-useless-concat: 2 91 | no-void: 2 92 | no-warning-comments: 0 93 | no-with: 2 94 | radix: 2 95 | vars-on-top: 0 96 | wrap-iife: 2 97 | yoda: 0 98 | 99 | # Strict 100 | strict: 0 101 | 102 | # Variables 103 | init-declarations: 0 104 | no-catch-shadow: 2 105 | no-delete-var: 2 106 | no-label-var: 2 107 | no-shadow-restricted-names: 2 108 | no-shadow: 0 109 | no-undef-init: 2 110 | no-undef: 0 111 | no-undefined: 0 112 | no-unused-vars: 0 113 | no-use-before-define: 0 114 | 115 | # Node.js and CommonJS 116 | callback-return: 2 117 | global-require: 0 118 | handle-callback-err: 2 119 | no-mixed-requires: 0 120 | no-new-require: 0 121 | no-path-concat: 2 122 | no-process-exit: 2 123 | no-restricted-modules: 0 124 | no-sync: 0 125 | 126 | # Stylistic Issues 127 | array-bracket-spacing: 0 128 | block-spacing: 0 129 | brace-style: 0 130 | camelcase: 0 131 | comma-spacing: 0 132 | comma-style: 0 133 | computed-property-spacing: 0 134 | consistent-this: 0 135 | eol-last: 0 136 | func-names: 0 137 | func-style: 0 138 | id-length: 0 139 | id-match: 0 140 | indent: 0 141 | jsx-quotes: 0 142 | key-spacing: 0 143 | linebreak-style: 0 144 | lines-around-comment: 0 145 | max-depth: 0 146 | max-len: 0 147 | max-nested-callbacks: 0 148 | max-params: 0 149 | max-statements: [2, 30] 150 | new-cap: 0 151 | new-parens: 0 152 | newline-after-var: 0 153 | no-array-constructor: 0 154 | no-bitwise: 0 155 | no-continue: 0 156 | no-inline-comments: 0 157 | no-lonely-if: 0 158 | no-mixed-spaces-and-tabs: 0 159 | no-multiple-empty-lines: 0 160 | no-negated-condition: 0 161 | no-nested-ternary: 0 162 | no-new-object: 0 163 | no-plusplus: 0 164 | no-restricted-syntax: 0 165 | no-spaced-func: 0 166 | no-ternary: 0 167 | no-trailing-spaces: 0 168 | no-underscore-dangle: 0 169 | no-unneeded-ternary: 0 170 | object-curly-spacing: 0 171 | one-var: 0 172 | operator-assignment: 0 173 | operator-linebreak: 0 174 | padded-blocks: 0 175 | quote-props: 0 176 | quotes: 0 177 | require-jsdoc: 0 178 | semi-spacing: 0 179 | semi: 0 180 | sort-vars: 0 181 | space-after-keywords: 0 182 | space-before-blocks: 0 183 | space-before-function-paren: 0 184 | space-before-keywords: 0 185 | space-in-parens: 0 186 | space-infix-ops: 0 187 | space-return-throw-case: 0 188 | space-unary-ops: 0 189 | spaced-comment: 0 190 | wrap-regex: 0 191 | 192 | # ECMAScript 6 193 | arrow-body-style: 0 194 | arrow-parens: 0 195 | arrow-spacing: 0 196 | constructor-super: 0 197 | generator-star-spacing: 0 198 | no-arrow-condition: 0 199 | no-class-assign: 0 200 | no-const-assign: 0 201 | no-dupe-class-members: 0 202 | no-this-before-super: 0 203 | no-var: 0 204 | object-shorthand: 0 205 | prefer-arrow-callback: 0 206 | prefer-const: 0 207 | prefer-reflect: 0 208 | prefer-spread: 0 209 | prefer-template: 0 210 | require-yield: 0 211 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | coverage 4 | out 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | out -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4.1' 4 | addons: 5 | code_climate: 6 | repo_token: 1ab8d32d74f270b9feaf3a47edce2c8d569cf164b2f7615374542952400d2e02 7 | after_script: 8 | - if [ $TRAVIS_BRANCH = 'master' ]; then cat coverage/lcov.info | codeclimate; fi 9 | deploy: 10 | provider: npm 11 | email: danielallenreid@gmail.com 12 | api_key: 13 | secure: N1/mVa2+pzX+F+QL3CqXPisC/38c6+HXv9d7XA2AbItYc8m3nUJxkKorlx08oVwvErqZ5GVvANUCtjUmDIU9ObPdH0IlC33rqZ4x6/NxwmIIugqH9LHcwPd6ivACFq8Czbehhn+TjK6QKA82cP7qM7g+FSCJwBkC0HQaUDG215I= 14 | on: 15 | tags: true 16 | repo: dareid/chakram 17 | branch: master 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #Changelog 2 | 3 | ###1.5.0 4 | - Added `responsetime` assertion #58 5 | - Added `responseTime` to `ChakramResponseObject` #60 6 | - Introduced `del` alias #61 7 | 8 | ###1.4.0 9 | - Added support for expect's second custom message argument #56 10 | - Exposes tv4's schemaBanUnknown and schemaCyclicCheck options #52 11 | - Exposes tv4's getSchemaMap method #43 12 | 13 | ###1.3.0 14 | - Identifies all schema errors #38 15 | - Exposes tv4's addFormat method #39 16 | - Added debugging functionality #30 17 | 18 | ###1.2.3 19 | - Updating dependencies 20 | - Fails schema validation if a referenced schema is missing #32 21 | 22 | ###1.2.2 23 | - Updating dependencies 24 | 25 | ###1.2.1 26 | - Allows equality checks for NULLs in JSON #24 27 | 28 | ###1.2.0 29 | - Added ability to set default options for requests #21 30 | 31 | ###1.1.0 32 | - Allowing pre-registration of JSON schemas 33 | 34 | ###1.0.1 35 | - Improved schema error messages #14 36 | - Updated dependencies #15 37 | 38 | ###1.0.0 39 | - Added "comprise" and "comprised" chain elements which should be used instead of "include" for subset JSON assertions 40 | - Simplified plugin interface 41 | 42 | ###0.1.0 43 | - Added callback support for JSON and header assertions 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Daniel Reid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chakram 2 | 3 | [](https://travis-ci.org/dareid/chakram) [](https://codeclimate.com/github/dareid/chakram) [](https://codeclimate.com/github/dareid/chakram) [](https://gitter.im/dareid/chakram) 4 | 5 | **Chakram is no longer actively maintained, PRs are welcomed** 6 | 7 | Chakram is an API testing framework designed to perform end to end tests on JSON REST endpoints. 8 | 9 | The library offers a BDD testing style and fully exploits javascript promises - the resulting tests are simple, clear and expressive. Chakram is built on [node.js](https://nodejs.org/), [mocha](http://mochajs.org/), [chai](http://chaijs.com/) and [request](https://github.com/request/request). 10 | 11 | This readme offers an introduction to the library. For more information, visit Chakram's [documentation](http://dareid.github.io/chakram/) and [tests](https://github.com/dareid/chakram/tree/master/test) which demonstrate all of Chakram's capabilities. In addition, example tests of publicly accessible APIs are available in the [examples directory](https://github.com/dareid/chakram/tree/master/examples). If required, assistance can be found in the project's [gitter chat room](https://gitter.im/dareid/chakram). 12 | 13 | ## Features 14 | * HTTP specific assertions. Allows testing of: 15 | * Status codes 16 | * Cookie presence and value 17 | * Header presence and value 18 | * JSON values 19 | * JSON structure (using the [JSON schema specification](http://json-schema.org/documentation.html)) 20 | * Compression 21 | * Response times 22 | * BDD formatting and hooks (e.g. beforeEach, afterEach) 23 | * Promise based 24 | * Plugin support 25 | * Custom assertions 26 | * Exports results in a variety of formats 27 | * Debugging support 28 | 29 | ## Plugins 30 | Awesome plugins from the community: 31 | - [Joi Schema Assertion](https://github.com/roberto/chakram-joi) 32 | 33 | We would love to see more plugins! If you have a plugin, please add it to the list. 34 | 35 | ## Getting Started 36 | 37 | ### Install Chakram 38 | Chakram requires Node.js and npm to be installed. It is available as an npm module. Ideally, Chakram should be added to your testing project's devDependencies. This can be achieved with the following command: 39 | ```js 40 | npm install chakram --save-dev 41 | ``` 42 | 43 | ### Writing Tests 44 | 45 | Chakram builds on top of the mocha testing framework. As such, the tests follow mocha's [BDD style](http://mochajs.org/#getting-started). The following sections introduce the various aspects of writing a Chakram test. 46 | 47 | #### Making Requests 48 | 49 | Chakram makes use of the [request library](https://github.com/request/request) and as such boasts a comprehensive request capability. Chakram exposes helper methods for the most common HTTP request verbs. The methods typically require the URL as the first parameter, the request body (if applicable) as the second parameter and any request options as an optional last parameter. For full documentation of the request methods see [here](http://dareid.github.io/chakram/jsdoc/module-chakram.html). The request methods return a promise which resolves to a [Chakram response object](http://dareid.github.io/chakram/jsdoc/global.html#ChakramResponse). 50 | 51 | Below is an example of making a HTTP GET request: 52 | ```js 53 | var chakram = require('chakram'); 54 | 55 | describe("Chakram", function() { 56 | it("should offer simple HTTP request capabilities", function () { 57 | return chakram.get("http://httpbin.org/get"); 58 | }); 59 | }); 60 | ``` 61 | 62 | #### Testing Responses 63 | 64 | Chakram offers a range of HTTP specific assertions which can test the information returned from API requests. Chakram offers a BDD testing style through Chakram's `expect` interface. 65 | 66 | When testing API responses, pass the request promise as an argument into chakram.expect. This will return an object which exposes the Chakram and Chai assertions. Perform an assertion by calling the desired [Chakram assertion method](http://dareid.github.io/chakram/jsdoc/module-chakram-expectation.html). [Chai properties](http://chaijs.com/api/bdd/) can be used as a prefix to the assertion, improving the test's readability. 67 | 68 | The assertion is performed once the response is received (i.e. the request promise is fulfilled). Chakram assertions return a promise which resolve to a [Chakram response object](http://dareid.github.io/chakram/jsdoc/global.html#ChakramResponse) once the test has been performed. 69 | 70 | Below is an example of testing the status code of a HTTP GET request: 71 | ```js 72 | var chakram = require('chakram'), 73 | expect = chakram.expect; 74 | 75 | describe("Chakram", function() { 76 | it("should provide HTTP specific assertions", function () { 77 | var response = chakram.get("http://httpbin.org/get"); 78 | return expect(response).to.have.status(200); 79 | }); 80 | }); 81 | ``` 82 | 83 | In addition to the HTTP specific assertions, chakram.expect exposes all of [Chai's BDD properties and methods](http://chaijs.com/api/bdd/). Documentation for the HTTP specific assertions can be seen [here](http://dareid.github.io/chakram/jsdoc/module-chakram-expectation.html). 84 | 85 | #### Waiting 86 | 87 | As this library focuses on testing REST APIs, the tests are naturally asynchronous. Mocha has [native support for promises](http://mochajs.org/#asynchronous-code), which Chakram exploits. Returning a promise from an `it` callback will cause the test to wait until the promise resolves before continuing. Chakram's requests and expectations return promises which fulfill to [Chakram response objects](http://dareid.github.io/chakram/jsdoc/global.html#ChakramResponse). These promises can be returned to ensure the test waits for them to complete (as can be seen in the previous two examples). 88 | 89 | It is important that tests wait for all requests and assertions to be completed. To help, chakram includes a wait method. This returns a promise which will be fulfilled once all assertions have been performed. Furthermore, Chakram will fail any tests which do not wait for assertions to complete. Below is a test using the wait method. 90 | 91 | ```js 92 | var chakram = require('chakram'), 93 | expect = chakram.expect; 94 | 95 | describe("Chakram", function() { 96 | it("should provide a simple async testing framework", function () { 97 | var response = chakram.get("http://httpbin.org/get"); 98 | expect(response).to.have.status(200); 99 | expect(response).not.to.have.header('non-existing-header'); 100 | return chakram.wait(); 101 | }); 102 | }); 103 | ``` 104 | 105 | #### Complex Promise Use 106 | 107 | Due to the use of promises, complex tests can be written requiring chains of requests and assertions. An example can be seen below: 108 | 109 | ```js 110 | describe("Chakram", function () { 111 | it("should support sequential API interaction", function () { 112 | var artist = "Notorious B.I.G."; 113 | return chakram.get("https://api.spotify.com/v1/search?q="+artist+"&type=artist") 114 | .then(function (searchResponse) { 115 | var bigID = searchResponse.body.artists.items[0].id; 116 | return chakram.get("https://api.spotify.com/v1/artists/"+bigID+"/top-tracks?country=GB"); 117 | }) 118 | .then(function (topTrackResponse) { 119 | var topTrack = topTrackResponse.body.tracks[0]; 120 | expect(topTrack.name).to.contain("Old Thing Back"); 121 | }); 122 | }); 123 | }); 124 | ``` 125 | 126 | Chakram exposes three promise related methods: 127 | - [all](http://dareid.github.io/chakram/jsdoc/module-chakram.html#.all), which takes an array of promises and returns a promise which is fulfilled once all promises in the provided array are fulfilled. The fulfillment value of the returned promise is an array of the fulfillment values of the promises which were passed to the function. 128 | - [wait](http://dareid.github.io/chakram/jsdoc/module-chakram.html#.wait), which returns a promise which is fulfilled once all Chakram expectations are fulfilled. 129 | - [waitFor](http://dareid.github.io/chakram/jsdoc/module-chakram.html#.waitFor), which takes an array of promises and returns a promise which is fulfilled once all promises in the provided array are fulfilled. This is similar to chakram.all, except it is fulfilled with the fulfillment value of the last promise in the provided array. 130 | 131 | ### Running Tests 132 | To run Chakram tests, install the Mocha testing framework globally (or as a dev dependency): 133 | ``` 134 | npm install -g mocha 135 | ``` 136 | Once installed, run the tests using the [Mocha command line](http://mochajs.org/#usage), which in its simplest form is: 137 | ``` 138 | mocha path/to/tests 139 | ``` 140 | Test results can be exported in multiple formats, Mocha's builtin formats are described [here](http://mochajs.org/#reporters) and export plugins for Mocha are available on NPM. 141 | 142 | ### Adding Assertions 143 | 144 | New assertions can be easily added to Chakram. The [plugin tests](https://github.com/dareid/chakram/blob/master/test/core/plugins.js) demonstrate how properties and methods can be added. Further information is available in [Chai's plugin documentation](http://chaijs.com/guide/plugins/). 145 | 146 | ## Contributing 147 | Issues, pull requests and questions are welcomed. 148 | 149 | ### Pull Requests 150 | 151 | - Fork the repository 152 | - Make changes 153 | - If required, write tests covering the new functionality (tests are normally written against [httpbin.org](http://httpbin.org/)) 154 | - Ensure all tests pass and 100% code coverage is achieved (run `npm test`) 155 | - Raise pull request 156 | -------------------------------------------------------------------------------- /conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": { 3 | "applicationName": "Chakram", 4 | "meta": { 5 | "title": "Chakram: BDD REST API test tool", 6 | "description": "Chakram: BDD REST API test tool", 7 | "keyword": "E2E REST API JSON chai mocha nodejs test BDD" 8 | }, 9 | "linenums": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/dweet.js: -------------------------------------------------------------------------------- 1 | var chakram = require('./../lib/chakram.js'), 2 | expect = chakram.expect; 3 | 4 | describe("Dweet API", function() { 5 | 6 | var namedDweetPost, initialDweetData, specifiedThingName; 7 | 8 | before("Initialize a new dweet thing for the tests", function () { 9 | specifiedThingName = 'chakram-test-thing'; 10 | initialDweetData = { 11 | description: "test sending a string", 12 | sensorValue: 0.2222, 13 | alert: true 14 | }; 15 | namedDweetPost = chakram.post("https://dweet.io/dweet/for/"+specifiedThingName, initialDweetData); 16 | }); 17 | 18 | it("should return 200 on success", function () { 19 | return expect(namedDweetPost).to.have.status(200); 20 | }); 21 | 22 | it("should specify success in the response 'this' field", function () { 23 | return expect(namedDweetPost).to.have.json('this', 'succeeded'); 24 | }); 25 | 26 | it("should respond with the created dweet's data", function () { 27 | return expect(namedDweetPost).to.have.json('with.content', initialDweetData); 28 | }); 29 | 30 | it("should use a dweet thing name if provided", function () { 31 | return expect(namedDweetPost).to.have.json('with.thing', specifiedThingName); 32 | }); 33 | 34 | it("should allow retrieval of the last data point", function () { 35 | var dataRetrieval = chakram.get("https://dweet.io/get/latest/dweet/for/"+specifiedThingName); 36 | return expect(dataRetrieval).to.have.json('with[0].content', initialDweetData); 37 | }); 38 | 39 | it("should respond with data matching the dweet schema", function () { 40 | var expectedSchema = { 41 | type: "object", 42 | properties: { 43 | this: {type: "string"}, 44 | by: {type: "string"}, 45 | the: {type: "string"}, 46 | with: { 47 | type: "object", 48 | properties: { 49 | thing: {type: "string"}, 50 | created: {type: "string"}, 51 | content: {type: "object"} 52 | }, 53 | required: ["thing", "created", "content"] 54 | } 55 | }, 56 | required: ["this", "by", "the", "with"] 57 | }; 58 | return expect(namedDweetPost).to.have.schema(expectedSchema); 59 | }); 60 | 61 | describe("anonymous thing name", function () { 62 | 63 | var generatedThingName, anonymousDweetPost; 64 | 65 | before(function () { 66 | anonymousDweetPost = chakram.post("https://dweet.io/dweet", initialDweetData); 67 | return anonymousDweetPost.then(function(respObj) { 68 | generatedThingName = respObj.body.with.thing; 69 | }); 70 | }); 71 | 72 | it("should succeed without a specified thing name, generating a random dweet thing name", function () { 73 | expect(anonymousDweetPost).to.have.status(200); 74 | expect(anonymousDweetPost).to.have.json('this', 'succeeded'); 75 | return chakram.wait(); 76 | }); 77 | 78 | it("should allow data retrieval using the generated thing name", function () { 79 | var data = chakram.get("https://dweet.io/get/latest/dweet/for/"+generatedThingName); 80 | return expect(data).to.have.json('with', function (dweetArray) { 81 | expect(dweetArray).to.have.length(1); 82 | var dweet = dweetArray[0]; 83 | expect(dweet.content).to.deep.equal(initialDweetData); 84 | expect(dweet.thing).to.equal(generatedThingName); 85 | }); 86 | }); 87 | }); 88 | 89 | 90 | }); 91 | -------------------------------------------------------------------------------- /examples/randomuser.js: -------------------------------------------------------------------------------- 1 | var chakram = require('./../lib/chakram.js'), 2 | expect = chakram.expect; 3 | 4 | describe("Random User API", function() { 5 | var apiResponse; 6 | 7 | before(function () { 8 | apiResponse = chakram.get("http://api.randomuser.me/0.6?gender=female"); 9 | return apiResponse; 10 | }); 11 | 12 | it("should return 200 on success", function () { 13 | return expect(apiResponse).to.have.status(200); 14 | }); 15 | 16 | it("should return content type and server headers", function () { 17 | expect(apiResponse).to.have.header("server"); 18 | expect(apiResponse).to.have.header("content-type", /text/); 19 | return chakram.wait(); 20 | }); 21 | 22 | it("should include email, username, password and phone number", function () { 23 | return expect(apiResponse).to.have.schema('results[0].user', { 24 | "required": [ 25 | "email", 26 | "username", 27 | "password", 28 | "phone" 29 | ] 30 | }); 31 | }); 32 | 33 | it("should return a female user", function () { 34 | return expect(apiResponse).to.have.json('results[0].user.gender', 'female'); 35 | }); 36 | 37 | it("should return a valid email address", function () { 38 | return expect(apiResponse).to.have.json(function(json) { 39 | var email = json.results[0].user.email; 40 | expect(/\S+@\S+\.\S+/.test(email)).to.be.true; 41 | }); 42 | }); 43 | 44 | it("should return a single random user", function () { 45 | return expect(apiResponse).to.have.schema('results', {minItems: 1, maxItems: 1}); 46 | }); 47 | 48 | it("should not be gzip compressed", function () { 49 | return expect(apiResponse).not.to.be.encoded.with.gzip; 50 | }); 51 | 52 | it("should return a different username on each request", function () { 53 | this.timeout(10000); 54 | var multipleResponses = []; 55 | for(var ct = 0; ct < 5; ct++) { 56 | multipleResponses.push(chakram.get("http://api.randomuser.me/0.6?gender=female")); 57 | } 58 | return chakram.all(multipleResponses).then(function(responses) { 59 | var returnedUsernames = responses.map(function(response) { 60 | return response.body.results[0].user.username; 61 | }); 62 | while (returnedUsernames.length > 0) { 63 | var username = returnedUsernames.pop(); 64 | expect(returnedUsernames.indexOf(username)).to.equal(-1); 65 | } 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /lib/assertions/arrayIncluding.js: -------------------------------------------------------------------------------- 1 | var path = require('./../utils/objectPath.js'); 2 | 3 | /** 4 | Checks the inclusion of an array member in the response. Defaults to the body JSON. An optional first argument allows sub-element checks. 5 | @alias module:chakram-expectation.arrayIncluding 6 | @param {String} [subelement] - if specified a sub-element of the JSON body is checked, specified using dot notation 7 | @param {String|Object} member - the expected member included in the array 8 | @example 9 | it("should check array in body", function () { 10 | var response = chakram.post("http://httpbin.org/post", ["an", "array"]); 11 | expect(response).to.have.arrayIncluding("json", "an"); 12 | return chakram.wait(); 13 | }); 14 | */ 15 | module.exports = function (chai, utils) { 16 | 17 | utils.addMethod(chai.Assertion.prototype, 'arrayIncluding', function () { 18 | var object = this._obj.body; 19 | if (arguments.length === 2) { 20 | object = path.get(utils, object, arguments[0]); 21 | } 22 | var assert = new chai.Assertion(object); 23 | utils.transferFlags(this, assert, false); 24 | assert.to.include(arguments[arguments.length-1]); 25 | }); 26 | }; -------------------------------------------------------------------------------- /lib/assertions/arrayLength.js: -------------------------------------------------------------------------------- 1 | var path = require('./../utils/objectPath.js'); 2 | 3 | /** 4 | Checks the length of an array in the response. Defaults to the body JSON. An optional first argument allows sub-element checks. 5 | @alias module:chakram-expectation.arrayLength 6 | @param {String} [subelement] - if specified a sub-element of the JSON body is checked, specified using dot notation 7 | @param {Number} length - the expected length of the array 8 | @example 9 | it("should check length of array", function () { 10 | var response = chakram.post("http://httpbin.org/post", ["an", "array"]); 11 | expect(response).to.have.arrayLength(2); 12 | return chakram.wait(); 13 | }); 14 | */ 15 | module.exports = function (chai, utils) { 16 | 17 | utils.addMethod(chai.Assertion.prototype, 'arrayLength', function () { 18 | var object = this._obj.body; 19 | if (arguments.length === 2) { 20 | object = path.get(utils, object, arguments[0]); 21 | } 22 | var assert = new chai.Assertion(object); 23 | utils.transferFlags(this, assert, false); 24 | assert.to.have.lengthOf(arguments[arguments.length-1]); 25 | }); 26 | }; -------------------------------------------------------------------------------- /lib/assertions/compression.js: -------------------------------------------------------------------------------- 1 | module.exports = function (chai, utils) { 2 | 3 | var confirmCompression = function(expectedCompression) { 4 | this.to.have.header('content-encoding', expectedCompression); 5 | }; 6 | 7 | /** 8 | Checks that the response is gzip compressed 9 | @alias module:chakram-expectation.gzip 10 | @example 11 | it("should detect gzip compression", function () { 12 | var gzip = chakram.get("http://httpbin.org/gzip"); 13 | return expect(gzip).to.be.encoded.with.gzip; 14 | }); 15 | */ 16 | var gzipAssertion = function () { 17 | confirmCompression.call(this, 'gzip'); 18 | }; 19 | 20 | /** 21 | Checks that the response is deflate compressed 22 | @alias module:chakram-expectation.deflate 23 | @example 24 | it("should detect deflate compression", function () { 25 | var deflate = chakram.get("http://httpbin.org/deflate"); 26 | return expect(deflate).to.be.encoded.with.deflate; 27 | }); 28 | */ 29 | var deflateAssertion = function () { 30 | confirmCompression.call(this, 'deflate'); 31 | }; 32 | 33 | utils.addProperty(chai.Assertion.prototype, 'encoded', function () {}); 34 | utils.addProperty(chai.Assertion.prototype, 'gzip', gzipAssertion); 35 | utils.addProperty(chai.Assertion.prototype, 'deflate', deflateAssertion); 36 | }; -------------------------------------------------------------------------------- /lib/assertions/comprise.js: -------------------------------------------------------------------------------- 1 | module.exports = function (chai, utils) { 2 | 3 | var setContainsFlag = function () { 4 | utils.flag(this,'contains',true); 5 | }; 6 | 7 | utils.addProperty(chai.Assertion.prototype, "comprise", setContainsFlag); 8 | utils.addProperty(chai.Assertion.prototype, "comprised", setContainsFlag); 9 | }; 10 | -------------------------------------------------------------------------------- /lib/assertions/cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | Either checks that a cookie exists or ensures the cookie matches a given value 3 | @alias module:chakram-expectation.cookie 4 | @param {String} name - checks a cookie with this name exists 5 | @param {String | RegExp} [value] - if specified, checks the cookie matches the given string or regular expression 6 | @example 7 | it("should allow checking of HTTP cookies", function () { 8 | var response = chakram.get("http://httpbin.org/cookies/set?chakram=testval"); 9 | expect(response).to.have.cookie('chakram'); 10 | expect(response).to.have.cookie('chakram', 'testval'); 11 | expect(response).to.have.cookie('chakram', /val/); 12 | return chakram.wait(); 13 | }); 14 | */ 15 | module.exports = function (chai, utils) { 16 | 17 | var getCookie = function (jar, url, key) { 18 | var cookies = jar.getCookies(url); 19 | for(var ct = 0; ct < cookies.length; ct++) { 20 | if(cookies[ct].key === key) { 21 | return cookies[ct]; 22 | } 23 | } 24 | return null; 25 | }; 26 | 27 | var getCookieValue = function (jar, url, key) { 28 | var cookie = getCookie(jar, url, key); 29 | return (cookie === null ? null : cookie.value); 30 | }; 31 | 32 | utils.addMethod(chai.Assertion.prototype, 'cookie', function (key, value) { 33 | var cookieValue = getCookieValue(this._obj.jar, this._obj.url, key); 34 | if(arguments.length === 1) { 35 | this.assert( 36 | cookieValue !== undefined && cookieValue !== null, 37 | 'expected cookie '+ key +' to exist', 38 | 'expected cookie '+ key +' not to exist' 39 | ); 40 | } else if (value instanceof RegExp) { 41 | this.assert( 42 | value.test(cookieValue), 43 | 'expected cookie '+ key + ' with value ' + cookieValue + ' to match regex '+value, 44 | 'expected cookie '+ key + ' with value ' + cookieValue + ' not to match regex '+value 45 | ); 46 | } else { 47 | this.assert( 48 | cookieValue === value, 49 | 'expected cookie '+ key + ' with value ' + cookieValue + ' to match '+value, 50 | 'expected cookie '+ key + ' with value ' + cookieValue + ' not to match '+value 51 | ); 52 | } 53 | }); 54 | }; -------------------------------------------------------------------------------- /lib/assertions/header.js: -------------------------------------------------------------------------------- 1 | /** 2 | Either checks that a header exists or ensures the header matches a given value 3 | @alias module:chakram-expectation.header 4 | @param {String} name - checks a header with this name exists 5 | @param {String | RegExp | function} [value] - if specified, checks the header matches the given string or regular expression OR calls the provided function passing the header's value 6 | @example 7 | it("should allow checking of HTTP headers", function () { 8 | var response = chakram.get("http://httpbin.org/get"); 9 | expect(response).to.have.header('content-type'); 10 | expect(response).to.have.header('content-type', 'application/json'); 11 | expect(response).to.have.header('content-type', /json/); 12 | expect(response).to.have.header('content-type', function(contentType) { 13 | expect(contentType).to.equal('application/json'); 14 | }); 15 | return chakram.wait(); 16 | }); 17 | */ 18 | 19 | module.exports = function (chai, utils) { 20 | 21 | utils.addMethod(chai.Assertion.prototype, 'header', function (key, expected) { 22 | 23 | var headerValue = this._obj.response.headers[key.toLowerCase()]; 24 | 25 | if(arguments.length === 1) { 26 | this.assert( 27 | headerValue !== undefined && headerValue !== null, 28 | 'expected header '+ key +' to exist', 29 | 'expected header '+ key +' not to exist' 30 | ); 31 | } else if (expected instanceof RegExp) { 32 | this.assert( 33 | expected.test(headerValue), 34 | 'expected header '+ key + ' with value ' + headerValue + ' to match regex '+expected, 35 | 'expected header '+ key + ' with value ' + headerValue + ' not to match regex '+expected 36 | ); 37 | } else if (typeof(expected) === 'function') { 38 | expected(headerValue); 39 | } else { 40 | this.assert( 41 | headerValue === expected, 42 | 'expected header '+ key + ' with value ' + headerValue + ' to match '+expected, 43 | 'expected header '+ key + ' with value ' + headerValue + ' not to match '+expected 44 | ); 45 | } 46 | }); 47 | }; -------------------------------------------------------------------------------- /lib/assertions/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | Chakram Expectation 3 | @module chakram-expectation 4 | @desc Extends the {@link http://chaijs.com/api/bdd/ chai.expect} object with additional HTTP matchers. 5 | */ 6 | 7 | module.exports = [ 8 | require('./statuscode.js'), 9 | require('./header.js'), 10 | require('./cookie.js'), 11 | require('./schema.js'), 12 | require('./json.js'), 13 | require('./compression.js'), 14 | require('./comprise.js'), 15 | require('./responsetime.js'), 16 | require('./arrayLength.js'), 17 | require('./arrayIncluding.js') 18 | ]; 19 | -------------------------------------------------------------------------------- /lib/assertions/json.js: -------------------------------------------------------------------------------- 1 | var path = require('./../utils/objectPath.js'); 2 | 3 | /** 4 | Checks the content of a JSON object within the return body. By default this will check the body JSON exactly matches the given object. If the 'comprise' chain element is used, it checks that the object specified is contained within the body JSON. An additional first argument allows sub object checks. 5 | @alias module:chakram-expectation.json 6 | @param {String} [subelement] - if specified a subelement of the JSON body is checked, specified using dot notation 7 | @param {*|function} expectedValue - a JSON serializable object which should match the JSON body or the JSON body's subelement OR a custom function which is called with the JSON body or the JSON body's subelement 8 | @example 9 | it("should allow checking of JSON return bodies", function () { 10 | var response = chakram.get("http://httpbin.org/get"); 11 | expect(response).to.comprise.of.json({ 12 | url: "http://httpbin.org/get", 13 | headers: { 14 | Host: "httpbin.org", 15 | } 16 | }); 17 | expect(response).to.have.json('url', "http://httpbin.org/get"); 18 | expect(response).to.have.json('url', function (url) { 19 | expect(url).to.equal("http://httpbin.org/get"); 20 | }); 21 | return chakram.wait(); 22 | }); 23 | */ 24 | 25 | module.exports = function (chai, utils) { 26 | var flag = utils.flag; 27 | utils.addMethod(chai.Assertion.prototype, 'json', function () { 28 | 29 | var object = this._obj.body; 30 | var toMatch = arguments[arguments.length-1]; 31 | 32 | if(arguments.length === 2) { 33 | object = path.get(utils, object, arguments[0]); 34 | } 35 | 36 | if(typeof(toMatch) === 'function') { 37 | toMatch(object); 38 | } else { 39 | var assert = new chai.Assertion(object); 40 | utils.transferFlags(this, assert, false); 41 | 42 | if(flag(this, 'contains')) { 43 | assert.to.containSubset(toMatch); 44 | } else { 45 | assert.to.deep.equal(toMatch); 46 | } 47 | } 48 | }); 49 | }; -------------------------------------------------------------------------------- /lib/assertions/responsetime.js: -------------------------------------------------------------------------------- 1 | /** 2 | Checks the response time of the response is less than or equal to the provided millisecond value. 3 | @alias module:chakram-expectation.responsetime 4 | @param {Number} milliseconds - the expected maximum response time in milliseconds 5 | @example 6 | it("should allow checking maximum response time", function () { 7 | var request = chakram.get("http://httpbin.org/delay/2"); 8 | return expect(request).to.have.responsetime(3000); 9 | }); 10 | */ 11 | 12 | module.exports = function (chai, utils) { 13 | 14 | utils.addMethod(chai.Assertion.prototype, 'responsetime', function (milliseconds) { 15 | var responseTime = this._obj.responseTime; 16 | this.assert( 17 | responseTime <= milliseconds, 18 | 'expected response time of ' + responseTime + 'ms to be less than or equal to ' + milliseconds + 'ms', 19 | 'expected response time of ' + responseTime + 'ms to not be less than or equal to ' + milliseconds + 'ms' 20 | ); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/assertions/schema.js: -------------------------------------------------------------------------------- 1 | var tv4 = require('tv4'), 2 | path = require('./../utils/objectPath.js'); 3 | 4 | /** 5 | Checks the schema of the returned JSON object against a provided {@link http://json-schema.org/ JSON Schema}. This assertion utilizes the brilliant {@link https://github.com/geraintluff/tv4 tv4 library}. An optional dot notation argument allows a subelement of the JSON object to checked against a JSON schema. Amoungst others, this can confirm types, array lengths, required fields, min and max of numbers and string lengths. For more examples see the test/assertions/schema.js tests. 6 | @alias module:chakram-expectation.schema 7 | @param {String} [subelement] - if specified a subelement of the JSON body is checked, specified using dot notation 8 | @param {*} expectedSchema - a JSON schema object which should match the JSON body or the JSON body's subelement. For more details on format see {@link http://json-schema.org/ the JSON schema website} 9 | @example 10 | it("should check that the returned JSON object satisifies a JSON schema", function () { 11 | var response = chakram.get("http://httpbin.org/get"); 12 | expect(response).to.have.schema('headers', {"required": ["Host", "Accept"]}); 13 | expect(response).to.have.schema({ 14 | "type": "object", 15 | properties: { 16 | url: { 17 | type: "string" 18 | }, 19 | headers: { 20 | type: "object" 21 | } 22 | } 23 | }); 24 | return chakram.wait(); 25 | }); 26 | */ 27 | 28 | module.exports = function (chai, utils) { 29 | 30 | var chakram = require('./../chakram'); 31 | 32 | utils.addMethod(chai.Assertion.prototype, 'schema', function () { 33 | 34 | var object = this._obj.body; 35 | var schema = arguments[arguments.length - 1]; 36 | var subElement = ''; 37 | 38 | if (arguments.length === 2) { 39 | subElement = arguments[0]; 40 | object = path.get(utils, object, subElement); 41 | } 42 | 43 | var validationResult = tv4.validateMultiple(object, schema, chakram.schemaCyclicCheck, chakram.schemaBanUnknown); 44 | 45 | var composeErrorMessage = function () { 46 | var errorMsg = 'expected body to match JSON schema ' + JSON.stringify(schema, null, 2) + '.'; 47 | if (validationResult.missing.length > 0) { 48 | errorMsg += '\n unresolved schemas: ' + JSON.stringify(validationResult.missing) + '.'; 49 | } 50 | if(Array.isArray(validationResult.errors)) { 51 | validationResult.errors.forEach(function (error) { 52 | errorMsg += '\n-----\n error: ' + error.message + '.\n data path: ' + subElement + error.dataPath + '.\n schema path: ' + error.schemaPath + '.'; 53 | }); 54 | } 55 | return errorMsg; 56 | }; 57 | 58 | this.assert( 59 | validationResult.valid && validationResult.missing.length === 0, 60 | composeErrorMessage(), 61 | 'expected body to not match JSON schema ' + JSON.stringify(schema) 62 | ); 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /lib/assertions/statuscode.js: -------------------------------------------------------------------------------- 1 | /** 2 | Checks the status code of the response 3 | @alias module:chakram-expectation.status 4 | @param {Number} code - the expected status code from the response 5 | @example 6 | it("should allow checking of the response's status code", function () { 7 | var response = chakram.get("http://httpbin.org/get"); 8 | return expect(response).to.have.status(200); 9 | }); 10 | */ 11 | 12 | module.exports = function (chai, utils) { 13 | 14 | utils.addMethod(chai.Assertion.prototype, 'status', function (status) { 15 | var respStatus = this._obj.response.statusCode; 16 | this.assert( 17 | respStatus === status, 18 | 'expected status code ' + respStatus + ' to equal ' + status, 19 | 'expected status code ' + respStatus + ' not to equal ' + status 20 | ); 21 | }); 22 | }; -------------------------------------------------------------------------------- /lib/chakram.js: -------------------------------------------------------------------------------- 1 | /** 2 | Chakram Module 3 | @module chakram 4 | @example 5 | var chakram = require("chakram"); 6 | */ 7 | 8 | var Q = require('q'), 9 | extend = require('extend-object'), 10 | methods = require('./methods.js'), 11 | plugins = require('./plugins.js'), 12 | debug = require('./debug.js'), 13 | tv4 = require('tv4'); 14 | 15 | var exports = module.exports = {}; 16 | extend(exports, methods, plugins, debug); 17 | 18 | var recordedExpects = []; 19 | 20 | /** 21 | Chakram assertion constructor. Extends chai's extend method with Chakram's HTTP assertions. 22 | Please see {@link http://chaijs.com/api/bdd/ chai's API documentation} for details on the default chai assertions and the {@link ChakramExpectation} documentation for the Chakram HTTP assertions. 23 | @param {*} value - The variable to run assertions on, can be a {@link ChakramResponse} promise 24 | @param {string} message - A custom assertion message passed to Chai 25 | @returns {chakram-expectation} A Chakram expectation object 26 | @alias module:chakram.expect 27 | @example 28 | var expect = chakram.expect; 29 | it("should support chakram and chai assertions", function () { 30 | var google = chakram.get("http://google.com"); 31 | expect(true).to.be.true; 32 | expect(google).to.have.status(200); 33 | expect(1).to.be.below(10); 34 | expect("teststring").to.be.a('string'); 35 | return chakram.wait(); 36 | }); 37 | */ 38 | exports.expect = function(value, message) { 39 | if(plugins.chai === null) { 40 | exports.initialize(); 41 | } 42 | if (value !== undefined && value !== null && value.then !== undefined) { 43 | var test = plugins.chai.expect(value, message).eventually; 44 | recordedExpects.push(test); 45 | return test; 46 | } else { 47 | return plugins.chai.expect(value, message); 48 | } 49 | }; 50 | 51 | /** 52 | Returns a promise which is fulfilled once all promises in the provided array are fulfilled. 53 | Identical to {@link https://github.com/kriskowal/q/wiki/API-Reference#promiseall Q.all}. 54 | @method 55 | @param {Promise[]} promiseArray - An array of promises to wait for 56 | @returns {Promise} 57 | @alias module:chakram.all 58 | */ 59 | exports.all = Q.all; 60 | 61 | /** 62 | Returns a promise which is fulfilled once all promises in the provided array are fulfilled. 63 | Similar to {@link https://github.com/kriskowal/q/wiki/API-Reference#promiseall Q.all}, however, instead of being fulfilled with an array containing the fulfillment value of each promise, it is fulfilled with the fulfillment value of the last promise in the provided array. This allows chaining of HTTP calls. 64 | @param {Promise[]} promiseArray - An array of promises to wait for 65 | @returns {Promise} 66 | @alias module:chakram.waitFor 67 | @example 68 | it("should support grouping multiple tests", function () { 69 | var response = chakram.get("http://httpbin.org/get"); 70 | return chakram.waitFor([ 71 | expect(response).to.have.status(200), 72 | expect(response).not.to.have.status(404) 73 | ]); 74 | }); 75 | */ 76 | exports.waitFor = function(promiseArray) { 77 | return Q.all(promiseArray).then(function(resolvedArray) { 78 | var deferred = Q.defer(); 79 | deferred.resolve(resolvedArray[resolvedArray.length - 1]); 80 | return deferred.promise; 81 | }); 82 | }; 83 | 84 | /** 85 | Returns a promise which is fulfilled once all chakram expectations are fulfilled. 86 | This works by recording all chakram expectations called within an 'it' and waits for all the expectations to finish before resolving the returned promise. 87 | @returns {Promise} 88 | @alias module:chakram.wait 89 | @example 90 | it("should support auto waiting for tests", function() { 91 | var response = chakram.get("http://httpbin.org/get"); 92 | expect(response).to.have.status(200); 93 | expect(response).not.to.have.status(404); 94 | return chakram.wait(); 95 | }); 96 | */ 97 | exports.wait = function() { 98 | return exports.waitFor(recordedExpects); 99 | }; 100 | 101 | /** 102 | Option for turning on tv4's "ban unknown properties" mode. 103 | @alias module:chakram.schemaBanUnknown 104 | @example 105 | chakram.schemaBanUnknown = true; 106 | */ 107 | exports.schemaBanUnknown = false; 108 | 109 | /** 110 | Option for turning on tv4's option to check for self-referencing objects. 111 | @alias module:chakram.schemaCyclicCheck 112 | @example 113 | chakram.schemaCyclicCheck = true; 114 | */ 115 | exports.schemaCyclicCheck = false; 116 | 117 | /** 118 | Exposes tv4's add schema method. Allows the registration of schemas used for schema validation. 119 | @alias module:chakram.addSchema 120 | @example 121 | chakram.addSchema('http://example.com/schema', { ... }); 122 | */ 123 | exports.addSchema = tv4.addSchema; 124 | 125 | /** 126 | Exposes tv4's get schemaMap method. Allows to check the mapping schema object. 127 | @alias module:chakram.getSchemaMap 128 | @example 129 | chakram.getSchemaMap(); 130 | */ 131 | exports.getSchemaMap = tv4.getSchemaMap; 132 | 133 | /** 134 | Exposes tv4's add format method. Allows add custom format for schema validation. 135 | @alias module:chakram.addSchemaFormat 136 | @example 137 | chakram.addSchemaFormat('decimal-digits', function (data, schema) { 138 | if (typeof data === 'string' && !/^[0-9]+$/.test(data)) { 139 | return null; 140 | } 141 | return "must be string of decimal digits"; 142 | }); 143 | */ 144 | exports.addSchemaFormat = tv4.addFormat; 145 | 146 | var warnUser = function (message) { 147 | if (this.currentTest.state !== 'failed') { 148 | this.test.error(new Error(message)); 149 | } 150 | }; 151 | 152 | var checkForUnfulfilledExpectations = function () { 153 | for(var ct = 0; ct < recordedExpects.length; ct++) { 154 | if(recordedExpects[ct].isFulfilled !== undefined && recordedExpects[ct].isFulfilled() === false) { 155 | warnUser.call(this, "Some expectation promises were not fulfilled before the test finished. Ensure you are waiting for all the expectations to run"); 156 | break; 157 | } 158 | } 159 | }; 160 | 161 | afterEach(function() { 162 | checkForUnfulfilledExpectations.call(this); 163 | recordedExpects = []; 164 | }); 165 | -------------------------------------------------------------------------------- /lib/debug.js: -------------------------------------------------------------------------------- 1 | var request = require('request'), 2 | debug = require('request-debug'); 3 | 4 | var exports = module.exports = {}; 5 | 6 | var isDebuggingOn = function () { 7 | return request.stopDebugging !== undefined; 8 | }; 9 | 10 | /** 11 | Deactivates debugging 12 | @method 13 | @alias module:chakram.stopDebug 14 | */ 15 | exports.stopDebug = function () { 16 | if(isDebuggingOn()) { 17 | request.stopDebugging(); 18 | } 19 | }; 20 | 21 | /** 22 | Actvates debugging. By default, will print request and response details to the console. 23 | Custom debugging functions can be specified. 24 | @method 25 | @param {function} debugFn - A debug function which replaces the default log to console. Details of parameters can be found at https://github.com/request/request-debug. 26 | @alias module:chakram.startDebug 27 | */ 28 | exports.startDebug = function (debugFn) { 29 | exports.stopDebug(); 30 | debug(request, debugFn); 31 | }; 32 | -------------------------------------------------------------------------------- /lib/methods.js: -------------------------------------------------------------------------------- 1 | var request = require('request'), 2 | extend = require('extend-object'), 3 | Q = require('q'); 4 | 5 | var exports = module.exports = {}; 6 | 7 | var defaultedRequestObj; 8 | var defaultsJar; 9 | var globalRequestJar = request.jar(); 10 | 11 | /** 12 | Perform HTTP request 13 | @param {string} method - the HTTP method to use 14 | @param {string} url - fully qualified url 15 | @param {Object} [params] - additional request options, see the popular {@link https://github.com/request/request#requestoptions-callback request library} for options 16 | @returns {Promise} Promise which will resolve to a {@link ChakramResponse} object 17 | @alias module:chakram.request 18 | @example 19 | var request = chakram.request("GET", "http://httpbin.org/get", { 20 | 'auth': {'user': 'username','pass': 'password'} 21 | }); 22 | expect(request).to.have.status(200); 23 | */ 24 | exports.request = function (method, url, params) { 25 | var options = extend({ 26 | url: url, 27 | method: method, 28 | json: true 29 | }, params || {} ); 30 | 31 | // options.jar is either a jar object or true 32 | // In case user passes {jar: true} option to request, we need reference to global jar to do assertions. 33 | options.jar = options.jar || defaultsJar; 34 | if (options.jar === true) { 35 | options.jar = globalRequestJar; 36 | } else if (options.jar === undefined) { 37 | // Create new jar for this request for backwards compatibility 38 | options.jar = request.jar(); 39 | } 40 | 41 | var deferred = Q.defer(); 42 | var reqObj = defaultedRequestObj || request; 43 | var timer = process.hrtime(); 44 | reqObj(options, function (error, response, body) { 45 | var elapsedTime = process.hrtime(timer); 46 | var elapsedMilliseconds = (elapsedTime[0] * 1000) + (elapsedTime[1] / 1000000); 47 | /** 48 | Chakram Response Object 49 | @desc Encapsulates the results of a HTTP call into a single object 50 | @typedef {Object} ChakramResponse 51 | @property {Error} error - An error when applicable 52 | @property {Object} response - An {@link http://nodejs.org/api/http.html#http_http_incomingmessage http.IncomingMessage} object 53 | @property {String|Buffer|Object} body - The response body. Typically a JSON object unless the json option has been set to false, in which case will be either a String or Buffer 54 | @property {Object} jar - A {@link https://github.com/goinstant/tough-cookie tough cookie} jar 55 | @property {String} url - The request's original URL 56 | @property {Number} responseTime - The time taken to make the request (including redirects) at millisecond resolution 57 | */ 58 | deferred.resolve({ 59 | error : error, 60 | response: response, 61 | body: body, 62 | jar: options.jar, 63 | url: url, 64 | responseTime: elapsedMilliseconds 65 | }); 66 | }); 67 | return deferred.promise; 68 | }; 69 | 70 | /** 71 | Perform HTTP GET request 72 | @param {string} url - fully qualified url 73 | @param {Object} [params] - additional request options, see the popular {@link https://github.com/request/request#requestoptions-callback request library} for options 74 | @returns {Promise} Promise which will resolve to a {@link ChakramResponse} object 75 | @alias module:chakram.get 76 | */ 77 | exports.get = function(url, params) { 78 | return exports.request('GET', url, params); 79 | }; 80 | 81 | /** 82 | Perform HTTP HEAD request 83 | @param {string} url - fully qualified url 84 | @param {Object} [params] - additional request options, see the popular {@link https://github.com/request/request#requestoptions-callback request library} for options 85 | @returns {Promise} Promise which will resolve to a {@link ChakramResponse} object 86 | @alias module:chakram.head 87 | */ 88 | exports.head = function(url, params) { 89 | return exports.request('HEAD', url, params); 90 | }; 91 | 92 | /** 93 | Perform HTTP OPTIONS request 94 | @param {string} url - fully qualified url 95 | @param {Object} [params] - additional request options, see the popular {@link https://github.com/request/request#requestoptions-callback request library} for options 96 | @returns {Promise} Promise which will resolve to a {@link ChakramResponse} object 97 | @alias module:chakram.options 98 | */ 99 | exports.options = function(url, params) { 100 | return exports.request('OPTIONS', url, params); 101 | }; 102 | 103 | var extendWithData = function (data, params) { 104 | return extend({body: data}, params); 105 | }; 106 | 107 | 108 | /** 109 | Perform HTTP POST request 110 | @param {string} url - fully qualified url 111 | @param {Object} data - a JSON serializable object (unless json is set to false in params, in which case this should be a buffer or string) 112 | @param {Object} [params] - additional request options, see the popular {@link https://github.com/request/request#requestoptions-callback request library} for options 113 | @returns {Promise} Promise which will resolve to a {@link ChakramResponse} object 114 | @alias module:chakram.post 115 | */ 116 | exports.post = function (url, data, params) { 117 | return exports.request('POST', url, extendWithData(data, params)); 118 | }; 119 | 120 | /** 121 | Perform HTTP PATCH request 122 | @param {string} url - fully qualified url 123 | @param {Object} data - a JSON serializable object (unless json is set to false in params, in which case this should be a buffer or string) 124 | @param {Object} [params] - additional request options, see the popular {@link https://github.com/request/request#requestoptions-callback request library} for options 125 | @returns {Promise} Promise which will resolve to a {@link ChakramResponse} object 126 | @alias module:chakram.patch 127 | */ 128 | exports.patch = function (url, data, params) { 129 | return exports.request('PATCH', url, extendWithData(data, params)); 130 | }; 131 | 132 | 133 | /** 134 | Perform HTTP PUT request 135 | @param {string} url - fully qualified url 136 | @param {Object} data - a JSON serializable object (unless json is set to false in params, in which case this should be a buffer or string) 137 | @param {Object} [params] - additional request options, see the popular {@link https://github.com/request/request#requestoptions-callback request library} for options 138 | @returns {Promise} Promise which will resolve to a {@link ChakramResponse} object 139 | @alias module:chakram.put 140 | */ 141 | exports.put = function (url, data, params) { 142 | return exports.request('PUT', url, extendWithData(data, params)); 143 | }; 144 | 145 | /** 146 | Perform HTTP DELETE request 147 | @param {string} url - fully qualified url 148 | @param {Object} [data] - a JSON serializable object (unless json is set to false in params, in which case this should be a buffer or string) 149 | @param {Object} [params] - additional request options, see the popular {@link https://github.com/request/request#requestoptions-callback request library} for options 150 | @returns {Promise} Promise which will resolve to a {@link ChakramResponse} object 151 | @alias module:chakram.delete 152 | */ 153 | exports.delete = function(url, data, params) { 154 | return exports.request('DELETE', url, extendWithData(data, params)); 155 | }; 156 | 157 | 158 | /** 159 | Alias for chakram.delete. Perform HTTP DELETE request. 160 | @param {string} url - fully qualified url 161 | @param {Object} [data] - a JSON serializable object (unless json is set to false in params, in which case this should be a buffer or string) 162 | @param {Object} [params] - additional request options, see the popular {@link https://github.com/request/request#requestoptions-callback request library} for options 163 | @returns {Promise} Promise which will resolve to a {@link ChakramResponse} object 164 | @alias module:chakram.del 165 | */ 166 | exports.del = function(url, data, params) { 167 | return exports.delete(url, data, params); 168 | }; 169 | 170 | /** 171 | Sets the default options applied to all future requests. 172 | @param {Object} [defaults] - default request options, see the popular {@link https://github.com/request/request#requestoptions-callback request library} for options 173 | @alias module:chakram.setRequestDefaults 174 | */ 175 | exports.setRequestDefaults = function(defaults) { 176 | defaultsJar = defaults.jar; // We want this copied also if it's undefined 177 | defaultedRequestObj = request.defaults(defaults); 178 | }; 179 | 180 | /** 181 | Clears any previously set default options. 182 | @alias module:chakram.clearRequestDefaults 183 | */ 184 | exports.clearRequestDefaults = function () { 185 | defaultedRequestObj = undefined; 186 | }; 187 | -------------------------------------------------------------------------------- /lib/plugins.js: -------------------------------------------------------------------------------- 1 | var chakramMatchers = require("./assertions/index.js"), 2 | chaiSubset = require('chai-subset'), 3 | chaiAsPromised, 4 | plugins = {}; 5 | 6 | var exports = module.exports = {}; 7 | exports.chai = null; 8 | 9 | var extendChaiPromise = function () { 10 | chaiAsPromised.transferPromiseness = function (assertion, promise) { 11 | assertion.then = promise.then.bind(promise); 12 | assertion.isFulfilled = promise.isFulfilled.bind(promise); 13 | }; 14 | }; 15 | 16 | var loadChai = function () { 17 | if (exports.chai !== null) { 18 | //need to remove to reinitialise with new plugins 19 | delete require.cache[require.resolve('chai-as-promised')]; 20 | delete require.cache[require.resolve('chai')]; 21 | } 22 | exports.chai = require('chai'); 23 | chaiAsPromised = require("chai-as-promised"); 24 | extendChaiPromise(); 25 | }; 26 | 27 | /** 28 | Initialise the chakram package with custom chai plugins. This is no longer recommended, instead use either addMethod, addProperty or addRawPlugin. 29 | @deprecated since 0.2.0 30 | @param {...ChaiPlugin} customChaiPlugin - One or multiple chai plugins 31 | @alias module:chakram.initialize 32 | @example 33 | var customProperty = function (chai, utils) { 34 | utils.addProperty(chai.Assertion.prototype, 'teapot', function () { 35 | var statusCode = this._obj.response.statusCode; 36 | this.assert( 37 | statusCode === 418, 38 | 'expected status code '+statusCode+' to equal 418', 39 | 'expected '+statusCode+' to not be equal to 418' 40 | ); 41 | }); 42 | }; 43 | chakram.initialise(customProperty); 44 | */ 45 | exports.initialize = function (customChaiPlugin) { 46 | loadChai(); 47 | for (var ct = 0; ct < arguments.length; ct++) { 48 | exports.chai.use(arguments[ct]); 49 | } 50 | for(var key in plugins) { 51 | exports.chai.use(plugins[key]); 52 | } 53 | chakramMatchers.map(function (matcher) { 54 | exports.chai.use(matcher); 55 | }); 56 | exports.chai.use(chaiSubset); 57 | exports.chai.use(chaiAsPromised); 58 | }; 59 | 60 | /** 61 | Add a raw chai plugin to Chakram. See Chai's documentation for more details. 62 | @param {String} name - The plugin's name, used as an identifier 63 | @param {function} plugin - A Chai plugin function, function should accept two arguments, the chai object and the chai utils object 64 | @alias module:chakram.addRawPlugin 65 | @example 66 | chakram.addRawPlugin("unavailable", function (chai, utils) { 67 | utils.addProperty(chai.Assertion.prototype, 'unavailable', function () { 68 | var statusCode = this._obj.response.statusCode; 69 | this.assert(statusCode === 503, 70 | 'expected status code '+statusCode+' to equal 503', 71 | 'expected '+statusCode+' to not be equal to 503'); 72 | }); 73 | }); 74 | var unavailableReq = chakram.get("http://httpbin.org/status/503"); 75 | return expect(unavailableReq).to.be.unavailable; 76 | */ 77 | exports.addRawPlugin = function (name, plugin) { 78 | plugins[name] = plugin; 79 | exports.initialize(); 80 | }; 81 | 82 | /** 83 | Add a new property assertion to Chakram. Properties should be used over methods when there is no arguments required for the assertion. 84 | @param {String} name - The plugin's name, used as an identifier 85 | @param {function} plugin - A function which should accept one argument; a {@link ChakramResponse} object 86 | @alias module:chakram.addProperty 87 | @example 88 | chakram.addProperty("httpbin", function (respObj) { 89 | var hostMatches = /httpbin.org/.test(respObj.url); 90 | this.assert(hostMatches, 91 | 'expected '+respObj.url+' to contain httpbin.org', 92 | 'expected '+respObj.url+' to not contain httpbin.org'); 93 | }); 94 | var httpbin = chakram.get("http://httpbin.org/status/200"); 95 | return expect(httpbin).to.be.at.httpbin; 96 | */ 97 | exports.addProperty = function (name, callback) { 98 | exports.addRawPlugin(name, function (chai, utils) { 99 | utils.addProperty(chai.Assertion.prototype, name, function () { 100 | callback.call(this, this._obj); 101 | }); 102 | }); 103 | }; 104 | 105 | /** 106 | Add a new method assertion to Chakram. Methods should be used when the assertion requires parameters. 107 | @param {String} name - The plugin's name, used as an identifier 108 | @param {function} plugin - A function which should accept one or more arguments. The first argument will be a {@link ChakramResponse} object, followed by any arguments passed into the assertion. 109 | @alias module:chakram.addMethod 110 | @example 111 | chakram.addMethod("statusRange", function (respObj, low, high) { 112 | var inRange = respObj.response.statusCode >= low && respObj.response.statusCode <= high; 113 | this.assert(inRange, 'expected '+respObj.response.statusCode+' to be between '+low+' and '+high, 'expected '+respObj.response.statusCode+' not to be between '+low+' and '+high); 114 | }); 115 | var twohundred = chakram.get("http://httpbin.org/status/200"); 116 | return expect(twohundred).to.have.statusRange(0, 200); 117 | */ 118 | exports.addMethod = function (name, callback) { 119 | exports.addRawPlugin(name, function (chai, utils) { 120 | utils.addMethod(chai.Assertion.prototype, name, function () { 121 | var args = Array.prototype.slice.call(arguments); 122 | args.unshift(this._obj); 123 | callback.apply(this, args); 124 | }); 125 | }); 126 | }; -------------------------------------------------------------------------------- /lib/utils/objectPath.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get : function (chaiUtils, obj, path) { 3 | var subObject = chaiUtils.getPathValue(path, obj); 4 | if(subObject === undefined) { 5 | throw new Error("could not find path '"+path+"' in object "+JSON.stringify(obj)); 6 | } 7 | return subObject; 8 | } 9 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chakram", 3 | "version": "1.5.0", 4 | "description": "Chakram is an API testing framework designed to test JSON REST endpoints. The library offers a BDD testing style and fully exploits javascript promises", 5 | "main": "lib/chakram.js", 6 | "license": "MIT", 7 | "contributors": [ 8 | { 9 | "name": "Daniel Reid", 10 | "email": "danielallenreid@gmail.com", 11 | "url": "https://twitter.com/danielallenreid" 12 | }, 13 | { 14 | "name": "Harry Rose" 15 | } 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/dareid/chakram" 20 | }, 21 | "keywords": [ 22 | "test", 23 | "API", 24 | "REST", 25 | "HTTP", 26 | "JSON", 27 | "mocha", 28 | "chai", 29 | "BDD" 30 | ], 31 | "dependencies": { 32 | "chai": "3.x.x", 33 | "chai-as-promised": "5.x.x", 34 | "chai-subset": "1.x.x", 35 | "extend-object": "1.x.x", 36 | "q": "1.x.x", 37 | "request": "2.x.x", 38 | "request-debug": "0.x.x", 39 | "tv4": "1.x.x" 40 | }, 41 | "devDependencies": { 42 | "codeclimate-test-reporter": "0.x.x", 43 | "istanbul": "0.x.x", 44 | "jaguarjs-jsdoc": "dareid/jaguarjs-jsdoc", 45 | "jsdoc": "latest", 46 | "mocha": "3.x.x", 47 | "rewire": "2.x.x", 48 | "simplifyify": "2.x.x", 49 | "sinon": "1.x.x" 50 | }, 51 | "scripts": { 52 | "pretest": "npm install", 53 | "test": "istanbul cover _mocha", 54 | "predoc": "npm install", 55 | "doc": "jsdoc -t node_modules/jaguarjs-jsdoc -c conf.json -R README.md -r lib", 56 | "prebuild:js": "rm -rf dist", 57 | "build:js": "simplifyify --outfile dist/chakram.js --bundle --minify --debug --standalone chakram lib/* lib/**/*" 58 | }, 59 | "engines": { 60 | "node": ">= 0.10.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/assertions/arrayIncluding.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Chakram Assertions", function () { 6 | 7 | describe("arrayIncluding()", function () { 8 | 9 | it("should check array at body root", function () { 10 | var album = { 11 | "userId": 1, 12 | "id": 1, 13 | "title": "quidem molestiae enim" 14 | }; 15 | var response = chakram.get("https://jsonplaceholder.typicode.com/albums"); 16 | expect(response).to.have.arrayIncluding(album); 17 | return chakram.wait(); 18 | }); 19 | 20 | it("should check array in body", function () { 21 | var response = chakram.post("http://httpbin.org/post", ["an", "array"]); 22 | expect(response).to.have.arrayIncluding("json", "an"); 23 | return chakram.wait(); 24 | }); 25 | 26 | it("should check array to not include", function () { 27 | var response = chakram.post("http://httpbin.org/post", ["an", "array"]); 28 | expect(response).to.not.have.arrayIncluding("json", "not"); 29 | return chakram.wait(); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/assertions/arrayLength.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Chakram Assertions", function () { 6 | 7 | describe("arrayLength()", function () { 8 | 9 | it("should check length of array at body root", function () { 10 | var response = chakram.get("https://jsonplaceholder.typicode.com/users"); 11 | expect(response).to.have.arrayLength(10); 12 | return chakram.wait(); 13 | }); 14 | 15 | it("should check length of array in body", function () { 16 | var response = chakram.post("http://httpbin.org/post", ["an", "array"]); 17 | expect(response).to.have.arrayLength("json", 2); 18 | return chakram.wait(); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/assertions/compression.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Chakram Assertions", function() { 6 | 7 | describe("Compression", function() { 8 | 9 | it("should allow assertions on uncompressed responses", function () { 10 | var noncompressed = chakram.get("http://httpbin.org/get"); 11 | expect(noncompressed).not.to.be.encoded.with.gzip; 12 | expect(noncompressed).not.to.be.encoded.with.deflate; 13 | return chakram.wait(); 14 | }); 15 | 16 | it("should detect gzip compression", function () { 17 | var gzip = chakram.get("http://httpbin.org/gzip"); 18 | expect(gzip).to.be.encoded.with.gzip; 19 | expect(gzip).not.to.be.encoded.with.deflate; 20 | return chakram.wait(); 21 | }); 22 | 23 | it("should detect deflate compression", function () { 24 | var deflate = chakram.get("http://httpbin.org/deflate"); 25 | expect(deflate).not.to.be.encoded.with.gzip; 26 | expect(deflate).to.be.encoded.with.deflate; 27 | return chakram.wait(); 28 | }); 29 | 30 | it("should support shorter language chains", function () { 31 | var deflate = chakram.get("http://httpbin.org/deflate"); 32 | expect(deflate).not.to.be.gzip; 33 | expect(deflate).to.be.deflate; 34 | return chakram.wait(); 35 | }); 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /test/assertions/cookie.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Chakram Assertions", function() { 6 | describe("Cookies", function() { 7 | 8 | var cookieSet; 9 | 10 | before(function() { 11 | cookieSet = chakram.get("http://httpbin.org/cookies/set?chakram=testval"); 12 | }); 13 | 14 | it("should check existance of a cookie", function () { 15 | expect(cookieSet).to.have.cookie('chakram'); 16 | expect(cookieSet).not.to.have.cookie('nonexistantcookie'); 17 | return chakram.wait(); 18 | }); 19 | 20 | it("should check that the cookie value matches a given string", function () { 21 | expect(cookieSet).to.have.cookie('chakram', 'testval'); 22 | 23 | expect(cookieSet).not.to.have.cookie('Chakram', 'testval'); 24 | expect(cookieSet).not.to.have.cookie('chakram', 'est'); 25 | expect(cookieSet).not.to.have.cookie('chakram', 'testva'); 26 | expect(cookieSet).not.to.have.cookie('chakram', 'Testval'); 27 | expect(cookieSet).not.to.have.cookie('chakram', ''); 28 | 29 | expect(cookieSet).not.to.have.cookie('nonexistantcookie', 'testval'); 30 | return chakram.wait(); 31 | }); 32 | 33 | it("should check that the cookie value satisifies regex", function () { 34 | expect(cookieSet).to.have.cookie('chakram', /testval/); 35 | expect(cookieSet).to.have.cookie('chakram', /TESTVAL/i); 36 | expect(cookieSet).to.have.cookie('chakram', /test.*/); 37 | expect(cookieSet).to.have.cookie('chakram', /te.*val/); 38 | expect(cookieSet).to.have.cookie('chakram', /est/); 39 | 40 | expect(cookieSet).not.to.have.cookie('chakram', /\s/); 41 | expect(cookieSet).not.to.have.cookie('chakram', /t[s]/); 42 | expect(cookieSet).not.to.have.cookie('chakram', /TESTVAL/); 43 | 44 | expect(cookieSet).not.to.have.cookie('nonexistantcookie', /testval/); 45 | return chakram.wait(); 46 | }); 47 | }); 48 | 49 | describe("Cookies internal state", function() { 50 | 51 | var cookieSet; 52 | 53 | it("should preserve cookies if defaults jar set to true", function() { 54 | chakram.setRequestDefaults({jar: true}); 55 | 56 | cookieSet = chakram.get("http://httpbin.org/cookies/set?chakram1=testval1"); 57 | cookieSet = chakram.get("http://httpbin.org/cookies/set?chakram2=testval2"); 58 | 59 | expect(cookieSet).to.have.cookie('chakram1', 'testval1'); 60 | expect(cookieSet).to.have.cookie('chakram2', 'testval2'); 61 | return chakram.wait(); 62 | }); 63 | 64 | it("should not preserve cookies between requests on default", function() { 65 | 66 | // Reset to default state 67 | chakram.setRequestDefaults({jar: undefined}); 68 | 69 | cookieSet = chakram.get("http://httpbin.org/cookies/set?chakram1=testval1"); 70 | expect(cookieSet).to.have.cookie('chakram1', 'testval1'); 71 | 72 | cookieSet = chakram.get("http://httpbin.org/cookies/set?chakram2=testval2"); 73 | expect(cookieSet).not.to.have.cookie('chakram1', 'testval1'); 74 | expect(cookieSet).to.have.cookie('chakram2', 'testval2'); 75 | return chakram.wait(); 76 | }); 77 | 78 | testsRunningInNode && it("should preserve cookies if defaults jar set to instance", function() { 79 | var request = require('request'); 80 | var jar = request.jar(); 81 | chakram.setRequestDefaults({jar: jar}); 82 | 83 | cookieSet = chakram.get("http://httpbin.org/cookies/set?chakram1=testval1"); 84 | cookieSet = chakram.get("http://httpbin.org/cookies/set?chakram2=testval2"); 85 | 86 | expect(cookieSet).to.have.cookie('chakram1', 'testval1'); 87 | expect(cookieSet).to.have.cookie('chakram2', 'testval2'); 88 | return chakram.wait(); 89 | }); 90 | 91 | 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/assertions/header.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Chakram Assertions", function() { 6 | describe("Header", function() { 7 | 8 | var headerRequest; 9 | 10 | before(function() { 11 | headerRequest = chakram.get("http://httpbin.org/response-headers?testheader=true123"); 12 | }); 13 | 14 | it("should check existance of a header", function () { 15 | expect(headerRequest).to.have.header('testheader'); 16 | expect(headerRequest).to.have.header('testHeaDer'); 17 | expect(headerRequest).not.to.have.header('notpresentheader'); 18 | return chakram.wait(); 19 | }); 20 | 21 | it("should check that header matches string", function () { 22 | expect(headerRequest).to.have.header('testheader', "true123"); 23 | expect(headerRequest).to.have.header('TESTHEADER', "true123"); 24 | 25 | expect(headerRequest).not.to.have.header('testheader', "123"); 26 | expect(headerRequest).not.to.have.header('testheader', "TRUE"); 27 | expect(headerRequest).not.to.have.header('testheader', "true"); 28 | expect(headerRequest).not.to.have.header('testheader', "tru"); 29 | 30 | expect(headerRequest).not.to.have.header('notpresentheader', "true123"); 31 | return chakram.wait(); 32 | }); 33 | 34 | it("should check that header satisfies regex", function () { 35 | expect(headerRequest).to.have.header('testheader', /true/); 36 | expect(headerRequest).to.have.header('testheader', /ru/); 37 | expect(headerRequest).to.have.header('testheader', /\d/); 38 | expect(headerRequest).to.have.header('testheader', /[t][r]/); 39 | expect(headerRequest).to.have.header('Testheader', /TRUE/i); 40 | 41 | expect(headerRequest).not.to.have.header('testheader', /tree/); 42 | expect(headerRequest).not.to.have.header('testheader', /\s/); 43 | expect(headerRequest).not.to.have.header('testheader', /[t][w|y|j]/); 44 | expect(headerRequest).not.to.have.header('testheader', /TRUE/); 45 | 46 | expect(headerRequest).not.to.have.header('notpresentheader', "/true123/"); 47 | return chakram.wait(); 48 | }); 49 | 50 | it("should call provided functions with the header value", function () { 51 | expect(headerRequest).to.have.header('testheader', function (headerValue) { 52 | expect(headerValue).to.equal("true123"); 53 | }); 54 | expect(headerRequest).to.have.header('notpresentheader', function (headerValue) { 55 | expect(headerValue).to.be.undefined; 56 | }); 57 | return chakram.wait(); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/assertions/json.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Chakram Assertions", function() { 6 | describe("JSON", function() { 7 | 8 | var postRequest; 9 | 10 | before(function() { 11 | postRequest = chakram.post("http://httpbin.org/post", { 12 | stringArray: ["test1", "test2", "test3"], 13 | number: 20, 14 | str: "test str", 15 | empty: null, 16 | obj: { 17 | test: "str" 18 | } 19 | }); 20 | }); 21 | 22 | it("should throw an error if path does not exist", function () { 23 | return postRequest.then(function (obj) { 24 | expect(function() { 25 | expect(obj).to.have.json('headers.non.existant', {}); 26 | }).to.throw(Error); 27 | }); 28 | }); 29 | 30 | it("should support checking that a path does not exist", function () { 31 | return expect(postRequest).not.to.have.json('headers.non.existant'); 32 | }); 33 | 34 | describe("Equals", function () { 35 | it("should ensure matches json exactly", function () { 36 | return chakram.waitFor([ 37 | expect(postRequest).to.have.json('json.stringArray', ["test1", "test2", "test3"]), 38 | expect(postRequest).to.have.json('json.number', 20), 39 | expect(postRequest).not.to.have.json('json.number', 22), 40 | expect(postRequest).to.have.json('json.obj', { 41 | test: "str" 42 | }) 43 | ]); 44 | }); 45 | it("should be able to equal nulls", function () { 46 | return expect(postRequest).to.have.json('json.empty', null); 47 | }); 48 | }); 49 | 50 | var testChainedCompriseProperty = function(description, buildChain) { 51 | describe(description, function () { 52 | it("should ensure body includes given json", function() { 53 | return chakram.waitFor([ 54 | buildChain(expect(postRequest).to).json({ 55 | json: { 56 | number: 20, 57 | str: "test str", 58 | stringArray: { 59 | 1:"test2" 60 | } 61 | } 62 | }), 63 | buildChain(expect(postRequest).to.not).json({ 64 | json: { number: 22 } 65 | }), 66 | buildChain(expect(postRequest).to).json({ 67 | json: { 68 | obj: { 69 | test: "str" 70 | } 71 | } 72 | }) 73 | ]); 74 | }); 75 | 76 | it("should support negated include JSON assertions", function () { 77 | return postRequest.then(function (resp) { 78 | expect(function() { 79 | buildChain(expect(resp).to.not).json({ 80 | json: { number: 20 } 81 | }); 82 | }).to.throw(Error); 83 | }); 84 | }); 85 | 86 | it("should be able to specify json path", function () { 87 | return chakram.waitFor([ 88 | buildChain(expect(postRequest).to).json('json', { 89 | number: 20, 90 | str: "test str", 91 | stringArray: {1:"test2"} 92 | }), 93 | buildChain(expect(postRequest).to).json('json.obj', { 94 | test: "str" 95 | }), 96 | buildChain(expect(postRequest).to.not).json('json.obj', { 97 | doesnt: "exist" 98 | }) 99 | ]); 100 | }); 101 | }); 102 | }; 103 | 104 | testChainedCompriseProperty("Comprise", function(assertion){ return assertion.comprise.of; }); 105 | testChainedCompriseProperty("Comprised", function(assertion){ return assertion.be.comprised.of; }); 106 | 107 | describe("Callbacks", function () { 108 | it("should allow custom callbacks to be used to run assertions", function () { 109 | return expect(postRequest).to.have.json('json.stringArray', function (data) { 110 | expect(data).to.deep.equal(["test1", "test2", "test3"]); 111 | }); 112 | }); 113 | 114 | it("should allow the whole JSON body to be checked", function () { 115 | return expect(postRequest).to.have.json(function (data) { 116 | expect(data.json.number).to.be.above(19).and.below(21); 117 | expect(data.json.number).not.to.equal(211); 118 | }); 119 | }); 120 | }); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/assertions/responsetime.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Chakram Assertions", function() { 6 | 7 | describe("Response Time", function() { 8 | 9 | var request; 10 | before(function () { 11 | request = chakram.get("http://httpbin.org/delay/2"); 12 | }) 13 | 14 | it("should check response time is less than or equal to expected response time", function () { 15 | expect(request).to.have.responsetime(3000); 16 | expect(request).not.to.have.responsetime(1900); 17 | return chakram.wait(); 18 | }) 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/assertions/schema.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Chakram Assertions", function() { 6 | describe("JSON Schema", function() { 7 | 8 | var getRequest, postRequest, personArraySchema; 9 | 10 | before(function() { 11 | getRequest = chakram.get("http://httpbin.org/get"); 12 | postRequest = chakram.post("http://httpbin.org/post", { 13 | stringArray: ["test1", "test2", "test3"], 14 | mixedArray: ["str", true, 20, 1.2222, true], 15 | objectArray: [{ 16 | name: "bob", 17 | age: 20 18 | }, { 19 | name: "jim", 20 | age: 72 21 | }], 22 | number: 20, 23 | str: "test str" 24 | }); 25 | 26 | personArraySchema = { 27 | items: { 28 | properties: { 29 | name: { type: "string" }, 30 | age: { 31 | type: "integer", 32 | minimum: 0 33 | } 34 | } 35 | } 36 | }; 37 | }); 38 | 39 | describe("dot notation access", function() { 40 | 41 | it("should perform assertions on subelements if first argument is a dot notation string", function () { 42 | var expectedSchema = {"required": ["Host", "Accept"]}; 43 | expect(getRequest).to.have.schema('headers', expectedSchema); 44 | expect(getRequest).not.to.have.schema(expectedSchema); 45 | return chakram.wait(); 46 | }); 47 | 48 | it("should thrown an error if dot notation is not valid", function () { 49 | return getRequest.then(function (obj) { 50 | expect(function() { 51 | expect(obj).to.have.schema('headers.non.existant', {}); 52 | }).to.throw(Error); 53 | }); 54 | }); 55 | 56 | it("should be case sensitive", function () { 57 | return getRequest.then(function (obj) { 58 | expect(function() { 59 | expect(obj).to.have.schema('Headers', {}); 60 | }).to.throw(Error); 61 | }); 62 | }); 63 | 64 | }); 65 | 66 | describe("objects", function () { 67 | 68 | it("should be able to specify required object values", function () { 69 | var expectedSchema = {"required": ["args", "headers", "origin"]}; 70 | var incorrectSchema = {"required": ["not", "existing"]}; 71 | return chakram.waitFor([ 72 | expect(getRequest).to.have.schema(expectedSchema), 73 | expect(getRequest).not.to.have.schema(incorrectSchema) 74 | ]); 75 | }); 76 | 77 | it("should allow exact matching of an object's properties", function () { 78 | var missingUrlSchema = { 79 | properties: { 80 | url: {}, 81 | headers: {}, 82 | origin: {}, 83 | args: {} 84 | }, 85 | additionalProperties: false 86 | }; 87 | return expect(getRequest).to.have.schema(missingUrlSchema); 88 | }); 89 | 90 | it("should assert types in json object", function () { 91 | var expectedTypes = { 92 | "type": "object", 93 | properties: { 94 | url: { 95 | type: "string" 96 | }, 97 | headers: { 98 | type: "object" 99 | } 100 | } 101 | }; 102 | return expect(getRequest).to.have.schema(expectedTypes); 103 | }); 104 | 105 | it("should allow assertions on object's properties", function () { 106 | var expectedTypes = { 107 | properties: { 108 | url: { 109 | type: "string" 110 | } 111 | } 112 | }; 113 | return expect(getRequest).to.have.schema(expectedTypes); 114 | }); 115 | 116 | }); 117 | 118 | describe("arrays", function () { 119 | 120 | it("should assert types in json arrays", function () { 121 | var mixedArray = { 122 | items: { 123 | type: ["string", "boolean", "number"] 124 | } 125 | }; 126 | var stringArray = { 127 | items: { 128 | type: "string" 129 | } 130 | }; 131 | return chakram.waitFor([ 132 | expect(postRequest).to.have.schema('json.stringArray', stringArray), 133 | expect(postRequest).to.have.schema('json.stringArray', mixedArray), 134 | expect(postRequest).not.to.have.schema('json.mixedArray', stringArray), 135 | expect(postRequest).to.have.schema('json.mixedArray', mixedArray) 136 | ]); 137 | }); 138 | 139 | it("should allow assertions on array's items", function () { 140 | var expectStringsToBeTestWithNumber = { 141 | items: { 142 | pattern: /test\d/ 143 | } 144 | }; 145 | return chakram.waitFor([ 146 | expect(postRequest).to.have.schema('json.stringArray', expectStringsToBeTestWithNumber), 147 | expect(postRequest).not.to.have.schema('json.mixedArray', expectStringsToBeTestWithNumber), 148 | expect(postRequest).to.have.schema('json.objectArray', personArraySchema) 149 | ]); 150 | }); 151 | 152 | it("should assert array length", function () { 153 | expect(postRequest).to.have.schema('json.stringArray', {minItems: 0, maxItems: 5}); 154 | expect(postRequest).to.have.schema('json.stringArray', {maxItems: 5}); 155 | expect(postRequest).to.have.schema('json.stringArray', {minItems: 3, maxItems: 5}); 156 | expect(postRequest).not.to.have.schema('json.stringArray', {minItems: 4, maxItems: 5}); 157 | expect(postRequest).not.to.have.schema('json.stringArray', {minItems: 1, maxItems: 2}); 158 | return chakram.wait(); 159 | }); 160 | 161 | it("should assert unique items in array", function () { 162 | expect(postRequest).to.have.schema('json.stringArray', {uniqueItems:true}); 163 | expect(postRequest).to.have.schema('json.mixedArray', {uniqueItems:false}); 164 | expect(postRequest).not.to.have.schema('json.mixedArray', {uniqueItems:true}); 165 | return chakram.wait(); 166 | }); 167 | 168 | }); 169 | 170 | describe("numbers", function() { 171 | 172 | it("should assert number min and max values", function () { 173 | expect(postRequest).to.have.schema('json.number', {minimum:0, maximum:100}); 174 | expect(postRequest).to.have.schema('json.number', {maximum:100}); 175 | expect(postRequest).to.have.schema('json.number', {minimum:19, maximum:21}); 176 | expect(postRequest).to.have.schema('json.number', {minimum:20, maximum:21}); 177 | expect(postRequest).not.to.have.schema('json.number', {minimum:20, maximum:21, exclusiveMinimum:true}); 178 | expect(postRequest).to.have.schema('json.number', {minimum:19, maximum:20}); 179 | expect(postRequest).not.to.have.schema('json.number', {minimum:19, maximum:20, exclusiveMaximum:true}); 180 | expect(postRequest).not.to.have.schema('json.number', {minimum:1, maximum:5}); 181 | return chakram.wait(); 182 | }); 183 | 184 | }); 185 | 186 | describe("strings", function() { 187 | 188 | it("should assert string matches regex", function () { 189 | expect(postRequest).to.have.schema('json.str', {pattern: /test/}); 190 | expect(postRequest).to.have.schema('json.str', {pattern: /str/}); 191 | expect(postRequest).to.have.schema('json.str', {pattern: /est\sst/}); 192 | expect(postRequest).not.to.have.schema('json.str', {pattern: /string/}); 193 | expect(postRequest).not.to.have.schema('json.str', {pattern: /\d/}); 194 | return chakram.wait(); 195 | }); 196 | 197 | it("should assert string length", function () { 198 | expect(postRequest).to.have.schema('json.str', {minLength: 0, maxLength: 100}); 199 | expect(postRequest).not.to.have.schema('json.str', {maxLength: 5}); 200 | expect(postRequest).not.to.have.schema('json.str', {minLength: 50}); 201 | return chakram.wait(); 202 | }); 203 | 204 | }); 205 | 206 | describe("registering schemas", function () { 207 | 208 | it("should be able to validate pre-registered schemas", function () { 209 | chakram.addSchema("https://github.com/dareid/chakram/testschema/person-array", personArraySchema); 210 | chakram.addSchema({ 211 | id: "https://github.com/dareid/chakram/testschema/string-array", 212 | pattern: /test\d/ 213 | }); 214 | return expect(postRequest).to.have.schema('json', { 215 | properties: { 216 | stringArray: { 217 | items: { 218 | $ref: "https://github.com/dareid/chakram/testschema/string-array" 219 | } 220 | }, 221 | objectArray: { 222 | $ref: "https://github.com/dareid/chakram/testschema/person-array" 223 | } 224 | } 225 | }); 226 | }); 227 | 228 | it("should fail assertions with unknown schemas", function () { 229 | return expect(postRequest).not.to.have.schema('json', { 230 | id: "https://github.com/dareid/chakram/testschema2", 231 | properties: { 232 | objectArray: { 233 | $ref: "https://github.com/dareid/chakram/testschema/person-array-not-registered#" 234 | } 235 | } 236 | }); 237 | }); 238 | 239 | }); 240 | 241 | }); 242 | }); 243 | -------------------------------------------------------------------------------- /test/assertions/statuscode.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Chakram Assertions", function() { 6 | 7 | describe("Status code", function() { 8 | it("should assert return status code", function() { 9 | var exists = chakram.get("http://httpbin.org/status/200"); 10 | var missing = chakram.get("http://httpbin.org/status/404"); 11 | return chakram.waitFor([ 12 | expect(exists).to.have.status(200), 13 | expect(missing).to.have.status(404) 14 | ]); 15 | }); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /test/bootstrap.js: -------------------------------------------------------------------------------- 1 | global.chakram = require('./../lib/chakram.js'); 2 | global.expect = global.chakram.expect; 3 | -------------------------------------------------------------------------------- /test/core/base.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Chakram", function() { 6 | 7 | it("should support chai's built in expectations", function () { 8 | expect(true).not.to.equal(false); 9 | expect(1).to.be.below(10); 10 | expect("teststring").to.be.a('string'); 11 | expect([1,2,3]).not.to.contain(4); 12 | expect(undefined).to.be.undefined; 13 | expect(null).to.be.null; 14 | }); 15 | 16 | describe("Async support", function () { 17 | 18 | describe("Async it", function() { 19 | var delayedResponse; 20 | this.timeout(11000); 21 | 22 | beforeEach(function() { 23 | delayedResponse = chakram.get("http://httpbin.org/delay/1"); 24 | }); 25 | 26 | it("should support mocha's promise returns", function () { 27 | return expect(delayedResponse).to.have.status(200); 28 | }); 29 | 30 | it("should support mocha's done callback", function (done) { 31 | expect(delayedResponse).to.have.status(200).then(function(){done();}); 32 | }); 33 | }); 34 | }); 35 | 36 | 37 | describe("Response Object", function () { 38 | 39 | var request; 40 | 41 | before(function () { 42 | request = chakram.get("http://httpbin.org/get"); 43 | }); 44 | 45 | it("should expose any errors in the chakram response object", function () { 46 | return chakram.get("not-valid") 47 | .then(function(obj) { 48 | expect(obj.error).to.exist.and.to.be.an("error"); 49 | }); 50 | }); 51 | 52 | it("should include the original URL in the chakram response object", function () { 53 | return chakram.get("not-valid") 54 | .then(function(obj) { 55 | expect(obj.url).to.exist.and.to.equal("not-valid"); 56 | }); 57 | }); 58 | 59 | var assertChakramResponseObject = function (obj) { 60 | expect(obj.body).to.exist; 61 | expect(obj.response).to.exist; 62 | expect(obj.error).to.be.null; 63 | expect(obj.url).to.exist; 64 | expect(obj.jar).to.exist; 65 | }; 66 | 67 | it("should resolve chakram request promises to a chakram response object", function () { 68 | return request.then(assertChakramResponseObject); 69 | }); 70 | 71 | it("should resolve chakram expect promises to a chakram response object", function () { 72 | var expectPromise = expect(request).to.have.status(200); 73 | return expectPromise.then(assertChakramResponseObject); 74 | }); 75 | 76 | it("should resolve chakram.waitFor promises to a chakram response object", function () { 77 | var waitPromise = chakram.waitFor([ 78 | expect(request).to.have.status(200), 79 | expect(request).not.to.have.status(400) 80 | ]); 81 | return waitPromise.then(assertChakramResponseObject); 82 | }); 83 | 84 | it("should resolve chakram.wait promises to a chakram response object", function () { 85 | expect(request).to.have.status(200); 86 | expect(request).not.to.have.status(400); 87 | return chakram.wait().then(assertChakramResponseObject); 88 | }); 89 | 90 | it("should record response time", function () { 91 | this.timeout(3000); 92 | return chakram.get("http://httpbin.org/delay/2") 93 | .then(function (obj) { 94 | expect(obj.responseTime).to.exist.and.to.be.at.least(2000).and.at.most(3000); 95 | }); 96 | }); 97 | }); 98 | 99 | describe("Multiple expects", function () { 100 | var request; 101 | 102 | beforeEach(function() { 103 | request = chakram.get("http://httpbin.org/status/200"); 104 | }); 105 | 106 | it("should support grouping multiple tests", function () { 107 | return chakram.waitFor([ 108 | expect(request).to.have.status(200), 109 | expect(request).not.to.have.status(404) 110 | ]); 111 | }); 112 | 113 | it("should support chaining of tests", function () { 114 | return expect(request).to.have.status(200).and.not.to.have.status(404); 115 | }); 116 | 117 | it("should support auto waiting for tests", function() { 118 | expect(request).to.have.status(200); 119 | expect(request).not.to.have.status(404); 120 | return chakram.wait(); 121 | }); 122 | }); 123 | 124 | describe("Chained requests", function () { 125 | it("should allow multiple chained requests", function () { 126 | this.timeout(4000); 127 | return expect(chakram.get("http://httpbin.org/status/200")).to.have.status(200) 128 | .then(function(obj) { 129 | var postRequest = chakram.post("http://httpbin.org/post", {"url": obj.url}); 130 | expect(postRequest).to.have.status(200); 131 | expect(postRequest).to.have.header('content-length'); 132 | return chakram.wait(); 133 | }).then(function(obj) { 134 | expect(obj.body.json.url).to.be.equal("http://httpbin.org/status/200"); 135 | }); 136 | }); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /test/core/debug.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect), 4 | sinon = require('sinon') 5 | 6 | describe("Debugging", function() { 7 | 8 | var debugFnSpy = sinon.spy(); 9 | 10 | it("should log to console by default", function () { 11 | chakram.startDebug(); 12 | request = chakram.get("http://httpbin.org/get"); 13 | return expect(request).to.have.status(200); 14 | }); 15 | 16 | it("should support custom debug functions", function () { 17 | chakram.startDebug(debugFnSpy); 18 | request = chakram.get("http://httpbin.org/get"); 19 | return request.then(function () { 20 | expect(debugFnSpy.callCount).to.equal(2); 21 | expect(debugFnSpy.getCall(0).args[0]).to.equal('request'); 22 | expect(debugFnSpy.getCall(1).args[0]).to.equal('response'); 23 | expect(debugFnSpy.getCall(0).args[1].debugId).to.equal(debugFnSpy.getCall(1).args[1].debugId); 24 | }); 25 | }); 26 | 27 | it("should be possible to stop debugging", function () { 28 | debugFnSpy.reset(); 29 | chakram.stopDebug(); 30 | request = chakram.get("http://httpbin.org/get"); 31 | return request.then(function () { 32 | expect(debugFnSpy.callCount).to.equal(0); 33 | }); 34 | }); 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /test/core/documentation-examples.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Documentation examples", function() { 6 | 7 | it("should support chakram and chai assertions", function () { 8 | var google = chakram.get("http://google.com"); 9 | expect(true).to.be.true; 10 | expect(google).to.have.status(200); 11 | expect(1).to.be.below(10); 12 | expect("teststring").to.be.a('string'); 13 | return chakram.wait(); 14 | }); 15 | 16 | it("should support grouping multiple tests", function () { 17 | var response = chakram.get("http://httpbin.org/get"); 18 | return chakram.waitFor([ 19 | expect(response).to.have.status(200), 20 | expect(response).not.to.have.status(404) 21 | ]); 22 | }); 23 | 24 | it("should support auto waiting for tests", function() { 25 | var response = chakram.get("http://httpbin.org/get"); 26 | expect(response).to.have.status(200); 27 | expect(response).not.to.have.status(404); 28 | return chakram.wait(); 29 | }); 30 | 31 | it("should detect deflate compression", function () { 32 | var deflate = chakram.get("http://httpbin.org/deflate"); 33 | return expect(deflate).to.be.encoded.with.deflate; 34 | }); 35 | 36 | it("should detect gzip compression", function () { 37 | var gzip = chakram.get("http://httpbin.org/gzip"); 38 | return expect(gzip).to.be.encoded.with.gzip; 39 | }); 40 | 41 | it("should allow checking of HTTP cookies", function () { 42 | var response = chakram.get("http://httpbin.org/cookies/set?chakram=testval"); 43 | expect(response).to.have.cookie('chakram'); 44 | expect(response).to.have.cookie('chakram', 'testval'); 45 | expect(response).to.have.cookie('chakram', /val/); 46 | return chakram.wait(); 47 | }); 48 | 49 | it("should allow checking of HTTP headers", function () { 50 | var response = chakram.get("http://httpbin.org/get"); 51 | expect(response).to.have.header('content-type'); 52 | expect(response).to.have.header('content-type', 'application/json'); 53 | expect(response).to.have.header('content-type', /json/); 54 | expect(response).to.have.header('content-type', function(contentType) { 55 | expect(contentType).to.equal('application/json'); 56 | }); 57 | return chakram.wait(); 58 | }); 59 | 60 | it("should allow checking of JSON return bodies", function () { 61 | var response = chakram.get("http://httpbin.org/get"); 62 | expect(response).to.comprise.of.json({ 63 | url: "http://httpbin.org/get", 64 | headers: { 65 | Host: "httpbin.org" 66 | } 67 | }); 68 | expect(response).to.have.json('url', "http://httpbin.org/get"); 69 | expect(response).to.have.json('url', function (url) { 70 | expect(url).to.equal("http://httpbin.org/get"); 71 | }); 72 | return chakram.wait(); 73 | }); 74 | 75 | it("should check that the returned JSON object satisifies a JSON schema", function () { 76 | var response = chakram.get("http://httpbin.org/get"); 77 | expect(response).to.have.schema('headers', {"required": ["Host", "Accept"]}); 78 | expect(response).to.have.schema({ 79 | "type": "object", 80 | properties: { 81 | url: { 82 | type: "string" 83 | }, 84 | headers: { 85 | type: "object" 86 | } 87 | } 88 | }); 89 | return chakram.wait(); 90 | }); 91 | 92 | it("should allow checking of the response's status code", function () { 93 | var response = chakram.get("http://httpbin.org/get"); 94 | return expect(response).to.have.status(200); 95 | }); 96 | 97 | }); 98 | -------------------------------------------------------------------------------- /test/core/methods.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Methods", function() { 6 | 7 | var testWriteMethods = function (testMethod, testUrl) { 8 | it("should support JSON requests", function () { 9 | var json = {"num": 2,"str": "test"}; 10 | var response = testMethod(testUrl, json); 11 | return response.then(function(resp) { 12 | expect(resp.body).to.be.an('object'); 13 | expect(resp.body.json).to.deep.equal(json); 14 | expect(resp.body.headers['Content-Type']).to.be.equal('application/json'); 15 | }); 16 | }); 17 | 18 | it("should support non-JSON requests", function () { 19 | var stringPost = "testing with a string post"; 20 | var response = testMethod(testUrl, stringPost, {json:false}); 21 | return response.then(function(resp) { 22 | expect(resp.body).to.be.a('string'); 23 | expect(JSON.parse(resp.body).data).to.be.equal(stringPost); 24 | expect(JSON.parse(resp.body).headers['Content-Type']).not.to.be.equal('application/json'); 25 | }); 26 | }); 27 | 28 | it("should support sending custom headers", function () { 29 | var customHeaders = { 30 | "Token": "dummy token value" 31 | }; 32 | var response = testMethod(testUrl, {}, { 33 | headers: customHeaders 34 | }); 35 | return expect(response).to.include.json('headers', customHeaders); 36 | }); 37 | }; 38 | 39 | describe("POST", function () { 40 | testWriteMethods(chakram.post, "http://httpbin.org/post"); 41 | 42 | testsRunningInNode && it("should allow posting files with multipart/form-data", function () { 43 | var fs = require('fs'); 44 | var response = chakram.post("https://httpbin.org/post", undefined, { 45 | formData: { 46 | pkgFile: fs.createReadStream('./package.json') 47 | } 48 | }); 49 | expect(response).to.have.json('files', function (files) { 50 | expect(files).to.have.key('pkgFile'); 51 | expect(files.pkgFile).to.contain('chakram'); 52 | }); 53 | return chakram.wait(); 54 | }); 55 | }); 56 | 57 | describe("PUT", function () { 58 | testWriteMethods(chakram.put, "http://httpbin.org/put"); 59 | }); 60 | 61 | describe("DELETE", function () { 62 | testWriteMethods(chakram.delete, "http://httpbin.org/delete"); 63 | testWriteMethods(chakram.del, "http://httpbin.org/delete"); 64 | }); 65 | 66 | describe("PATCH", function () { 67 | testWriteMethods(chakram.patch, "http://httpbin.org/patch"); 68 | }); 69 | 70 | it("should allow GET requests", function () { 71 | return chakram.get("http://httpbin.org/get?test=str") 72 | .then(function(obj) { 73 | expect(obj.body).to.be.an('object'); 74 | expect(obj.body.args.test).to.equal('str'); 75 | }); 76 | }); 77 | 78 | it("should allow HEAD requests", function () { 79 | var request = chakram.head("http://httpbin.org/get?test=str"); 80 | expect(request).to.have.status(200); 81 | expect(request).to.have.header('content-length'); 82 | return chakram.wait().then(function(obj) { 83 | expect(obj.body).to.be.undefined; 84 | }); 85 | }); 86 | 87 | it("should allow OPTIONS requests", function () { 88 | var request = chakram.options("http://httpbin.org/get?test=str"); 89 | expect(request).to.have.header('Access-Control-Allow-Credentials'); 90 | expect(request).to.have.header('Access-Control-Allow-Methods'); 91 | expect(request).to.have.header('Access-Control-Allow-Origin'); 92 | expect(request).to.have.header('Access-Control-Max-Age'); 93 | return chakram.wait(); 94 | }); 95 | 96 | describe("request defaults", function () { 97 | before(function () { 98 | chakram.setRequestDefaults({ 99 | headers: { 100 | Testing: 'default-option' 101 | } 102 | }); 103 | }); 104 | 105 | it("should allow default settings to be applied to multiple requests", function () { 106 | return chakram.get("http://httpbin.org/get").then(function(firstResp) { 107 | return chakram.get("http://httpbin.org/get").then(function (secondResp) { 108 | expect(firstResp.body.headers.Testing).to.equal('default-option'); 109 | expect(secondResp.body.headers.Testing).to.equal('default-option'); 110 | }); 111 | }); 112 | }); 113 | 114 | it("should allow clearing default settings", function () { 115 | chakram.clearRequestDefaults(); 116 | return chakram.get("http://httpbin.org/get").then(function(resp) { 117 | expect(resp.body.headers.Testing).to.be.undefined; 118 | }); 119 | }); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /test/core/plugins.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false), 2 | chakram = (testsRunningInNode ? global.chakram : window.chakram), 3 | expect = (testsRunningInNode ? global.expect : window.expect); 4 | 5 | describe("Plugins", function() { 6 | 7 | it("should allow new properties to be defined", function () { 8 | chakram.addProperty("httpbin", function (respObj) { 9 | var hostMatches = /httpbin.org/.test(respObj.url); 10 | this.assert(hostMatches, 'expected '+respObj.url+' to contain httpbin.org', 'expected '+respObj.url+' to not contain httpbin.org'); 11 | }); 12 | var httpbin = chakram.get("http://httpbin.org/status/200"); 13 | expect(httpbin).to.be.at.httpbin; 14 | var senseye = chakram.get("http://senseye.io"); 15 | expect(senseye).not.to.be.at.httpbin; 16 | return chakram.wait(); 17 | }); 18 | 19 | it("should allow new methods to be defined", function () { 20 | chakram.addMethod("statusRange", function (respObj, low, high) { 21 | var inRange = respObj.response.statusCode >= low && respObj.response.statusCode <= high; 22 | this.assert(inRange, 'expected '+respObj.response.statusCode+' to be between '+low+' and '+high, 'expected '+respObj.response.statusCode+' not to be between '+low+' and '+high); 23 | }); 24 | var twohundred = chakram.get("http://httpbin.org/status/200"); 25 | expect(twohundred).to.have.statusRange(0, 200); 26 | expect(twohundred).to.have.statusRange(200, 200); 27 | expect(twohundred).not.to.have.statusRange(300, 500); 28 | return chakram.wait(); 29 | }); 30 | 31 | 32 | describe("raw chai plugin registration", function () { 33 | before(function() { 34 | chakram.addRawPlugin("unavailable", function (chai, utils) { 35 | utils.addProperty(chai.Assertion.prototype, 'unavailable', function () { 36 | var statusCode = this._obj.response.statusCode; 37 | this.assert(statusCode === 503, 'expected status code '+statusCode+' to equal 503', 'expected '+statusCode+' to not be equal to 503'); 38 | }); 39 | }); 40 | }); 41 | it("should allow low level chai plugins to be registered", function() { 42 | var unavailableReq = chakram.get("http://httpbin.org/status/503"); 43 | return expect(unavailableReq).to.be.unavailable; 44 | }); 45 | }); 46 | 47 | 48 | describe("pre 0.2.0 plugin registration", function () { 49 | before(function() { 50 | var customProperty = function (chai, utils) { 51 | utils.addProperty(chai.Assertion.prototype, 'teapot', function () { 52 | var statusCode = this._obj.response.statusCode; 53 | this.assert(statusCode === 418, 'expected status code '+statusCode+' to equal 418', 'expected '+statusCode+' to not be equal to 418'); 54 | }); 55 | }; 56 | var customMethod = function (chai, utils) { 57 | utils.addMethod(chai.Assertion.prototype, 'httpVersion', function (ver) { 58 | var version = this._obj.response.httpVersion; 59 | this.assert(version === ver, 'expected '+version+' to equal #{exp}', 'expected '+version+' to not be equal to #{exp}', ver); 60 | }); 61 | }; 62 | chakram.initialize(customProperty, customMethod); 63 | }); 64 | 65 | it("should support adding custom properties", function () { 66 | var notATeapot = chakram.get("http://httpbin.org/status/200"); 67 | var aTeapot = chakram.get("http://httpbin.org/status/418"); 68 | return chakram.waitFor([ 69 | expect(notATeapot).to.not.be.teapot, 70 | expect(aTeapot).to.be.teapot 71 | ]); 72 | }); 73 | 74 | it("should support adding custom methods", function () { 75 | var aTeapot = chakram.get("http://httpbin.org/status/418"); 76 | return expect(aTeapot).to.have.httpVersion("1.1"); 77 | }); 78 | }); 79 | 80 | }); 81 | -------------------------------------------------------------------------------- /test/core/warnings.js: -------------------------------------------------------------------------------- 1 | var testsRunningInNode = (typeof global !== "undefined" ? true : false); 2 | 3 | testsRunningInNode && describe("User Warnings", function() { 4 | 5 | var rewire = require('rewire'), 6 | sinon = require('sinon'), 7 | chakram = rewire('./../../lib/chakram.js'), 8 | expect = chakram.expect; 9 | 10 | var warningStub, revertWarning; 11 | 12 | before(function () { 13 | warningStub = sinon.stub(); 14 | revertWarning = chakram.__set__("warnUser", warningStub); 15 | }); 16 | 17 | it("should warn user about unrun tests", function () { 18 | var request = chakram.get("http://httpbin.org/status/200"); 19 | expect(request).to.have.status(400); 20 | return request; 21 | }); 22 | 23 | it("should warn user about unrun tests even when 'it' is synchronous", function() { 24 | var request = chakram.get("http://httpbin.org/status/200"); 25 | expect(request).to.have.status(400); 26 | }); 27 | 28 | it("should set the test as an error on warning the user", function () { 29 | revertWarning(); 30 | var thisObj = { 31 | test: { 32 | error : sinon.stub() 33 | }, 34 | currentTest: { 35 | state : "passed" 36 | } 37 | }; 38 | var warning = chakram.__get__("warnUser"); 39 | warning.call(thisObj, "test error"); 40 | expect(thisObj.test.error.callCount).to.equal(1); 41 | }); 42 | 43 | it("should not warn the user if the test has failed anyway", function () { 44 | revertWarning(); 45 | var thisObj = { 46 | test: { 47 | error : sinon.stub() 48 | }, 49 | currentTest: { 50 | state : "failed" 51 | } 52 | }; 53 | var warning = chakram.__get__("warnUser"); 54 | warning.call(thisObj, "test error"); 55 | expect(thisObj.test.error.callCount).to.equal(0); 56 | }); 57 | 58 | after(function() { 59 | expect(warningStub.callCount).to.equal(2); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 105 | 106 | 109 | 110 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | -t 10000 ./test/bootstrap.js test/core/*.js test/assertions/*.js examples/*.js 2 | --------------------------------------------------------------------------------