UNPKG

codeceptjs

Version:

Supercharged End 2 End Testing Framework for NodeJS

361 lines (341 loc) 9.47 kB
const Helper = require('@codeceptjs/helper') const assert = require('assert') const joi = require('joi') /** * This helper allows performing assertions on JSON responses paired with following helpers: * * * REST * * GraphQL * * Playwright * * It can check status codes, response data, response structure. * * * ## Configuration * * * `requestHelper` - a helper which will perform requests. `REST` by default, also `Playwright` or `GraphQL` can be used. Custom helpers must have `onResponse` hook in their config, which will be executed when request is performed. * * ### Examples * * Zero-configuration when paired with REST: * * ```js * // inside codecept.conf.js *{ * helpers: { * REST: { * endpoint: 'http://site.com/api', * }, * JSONResponse: {} * } *} * ``` * Explicitly setting request helper if you use `makeApiRequest` of Playwright to perform requests and not paired REST: * * ```js * // inside codecept.conf.js * // ... * helpers: { * Playwright: { * url: 'https://localhost', * browser: 'chromium', * }, * JSONResponse: { * requestHelper: 'Playwright', * } * } * ``` * ## Access From Helpers * * If you plan to add custom assertions it is recommended to create a helper that will retrieve response object from JSONResponse: * * * ```js * // inside custom helper * const response = this.helpers.JSONResponse.response; * ``` * * ## Methods */ class JSONResponse extends Helper { constructor(config = {}) { super(config) this.options = { requestHelper: 'REST', } this.options = { ...this.options, ...config } } _beforeSuite() { this.response = null if (!this.helpers[this.options.requestHelper]) { throw new Error(`Error setting JSONResponse, helper ${this.options.requestHelper} is not enabled in config, helpers: ${Object.keys(this.helpers)}`) } // connect to REST helper this.helpers[this.options.requestHelper].config.onResponse = response => { this.response = response } } _before() { this.response = null } static _checkRequirements() { try { require('joi') } catch (e) { return ['joi'] } } /** * Checks that response code is equal to the provided one * * ```js * I.seeResponseCodeIs(200); * ``` * * @param {number} code */ seeResponseCodeIs(code) { this._checkResponseReady() assert.strictEqual(this.response.status, code, 'Response code is not the same as expected') } /** * Checks that response code is not equal to the provided one * * ```js * I.dontSeeResponseCodeIs(500); * ``` * * @param {number} code */ dontSeeResponseCodeIs(code) { this._checkResponseReady() assert.notStrictEqual(this.response.status, code) } /** * Checks that the response code is 4xx */ seeResponseCodeIsClientError() { this._checkResponseReady() assert(this.response.status >= 400 && this.response.status < 500) } /** * Checks that the response code is 3xx */ seeResponseCodeIsRedirection() { this._checkResponseReady() assert(this.response.status >= 300 && this.response.status < 400) } /** * Checks that the response code is 5xx */ seeResponseCodeIsServerError() { this._checkResponseReady() assert(this.response.status >= 500 && this.response.status < 600) } /** * Checks that the response code is 2xx * Use it instead of seeResponseCodeIs(200) if server can return 204 instead. * * ```js * I.seeResponseCodeIsSuccessful(); * ``` */ seeResponseCodeIsSuccessful() { this._checkResponseReady() assert(this.response.status >= 200 && this.response.status < 300) } /** * Checks for deep inclusion of a provided json in a response data. * * ```js * // response.data == { user: { name: 'jon', email: 'jon@doe.com' } } * * I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } }); * ``` * If an array is received, checks that at least one element contains JSON * ```js * // response.data == [{ user: { name: 'jon', email: 'jon@doe.com' } }] * * I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } }); * ``` * * @param {object} json */ seeResponseContainsJson(json = {}) { this._checkResponseReady() if (Array.isArray(this.response.data)) { let found = false for (const el of this.response.data) { try { this._assertContains(el, json) found = true break } catch (err) { continue } } assert(found, `No elements in array matched ${JSON.stringify(json)}`) } else { this._assertContains(this.response.data, json) } } /** * Checks for deep inclusion of a provided json in a response data. * * ```js * // response.data == { data: { user: 1 } } * * I.dontSeeResponseContainsJson({ user: 2 }); * ``` * If an array is received, checks that no element of array contains json: * ```js * // response.data == [{ user: 1 }, { user: 3 }] * * I.dontSeeResponseContainsJson({ user: 2 }); * ``` * * @param {object} json */ dontSeeResponseContainsJson(json = {}) { this._checkResponseReady() if (Array.isArray(this.response.data)) { for (const data of this.response.data) { try { this._assertContains(data, json) assert.fail(`Found matching element: ${JSON.stringify(data)}`) } catch (err) { // expected to fail continue } } } else { try { this._assertContains(this.response.data, json) assert.fail('Response contains the JSON') } catch (err) { // expected to fail } } } /** * Checks for deep inclusion of a provided json in a response data. * * ```js * // response.data == { user: { name: 'jon', email: 'jon@doe.com' } } * * I.seeResponseContainsKeys(['user']); * ``` * * If an array is received, check is performed for each element of array: * * ```js * // response.data == [{ user: 'jon' }, { user: 'matt'}] * * I.seeResponseContainsKeys(['user']); * ``` * * @param {array} keys */ seeResponseContainsKeys(keys = []) { this._checkResponseReady() if (Array.isArray(this.response.data)) { for (const data of this.response.data) { for (const key of keys) { assert(key in data, `Key "${key}" is not found in ${JSON.stringify(data)}`) } } } else { for (const key of keys) { assert(key in this.response.data, `Key "${key}" is not found in ${JSON.stringify(this.response.data)}`) } } } /** * Executes a callback function passing in `response` object and assert * Use it to perform custom checks of response data * * ```js * I.seeResponseValidByCallback(({ data, status }) => { * assert.strictEqual(status, 200); * assert('user' in data); * assert('company' in data); * }); * ``` * * @param {function} fn */ seeResponseValidByCallback(fn) { this._checkResponseReady() fn({ ...this.response, assert }) const body = fn.toString() fn.toString = () => `${body.split('\n')[1]}...` } /** * Checks that response data equals to expected: * * ```js * // response.data is { error: 'Not allowed' } * * I.seeResponseEquals({ error: 'Not allowed' }) * ``` * @param {object} resp */ seeResponseEquals(resp) { this._checkResponseReady() assert.deepStrictEqual(this.response.data, resp) } /** * Validates JSON structure of response using [joi library](https://joi.dev). * See [joi API](https://joi.dev/api/) for complete reference on usage. * * Use pre-initialized joi instance by passing function callback: * * ```js * // response.data is { name: 'jon', id: 1 } * * I.seeResponseMatchesJsonSchema(joi => { * return joi.object({ * name: joi.string(), * id: joi.number() * }) * }); * * // or pass a valid schema * const joi = require('joi'); * * I.seeResponseMatchesJsonSchema(joi.object({ * name: joi.string(), * id: joi.number(); * }); * ``` * * @param {any} fnOrSchema */ seeResponseMatchesJsonSchema(fnOrSchema) { this._checkResponseReady() let schema = fnOrSchema if (typeof fnOrSchema === 'function') { schema = fnOrSchema(joi) const body = fnOrSchema.toString() fnOrSchema.toString = () => `${body.split('\n')[1]}...` } if (!schema) throw new Error('Empty Joi schema provided, see https://joi.dev/ for details') if (!joi.isSchema(schema)) throw new Error('Invalid Joi schema provided, see https://joi.dev/ for details') schema.toString = () => schema.describe() joi.assert(this.response.data, schema) } _checkResponseReady() { if (!this.response) throw new Error('Response is not available') } _assertContains(actual, expected) { for (const key in expected) { assert(key in actual, `Key "${key}" not found in ${JSON.stringify(actual)}`) if (typeof expected[key] === 'object' && expected[key] !== null) { this._assertContains(actual[key], expected[key]) } else { assert.deepStrictEqual(actual[key], expected[key], `Values for key "${key}" don't match`) } } } } module.exports = JSONResponse