UNPKG

sinon

Version:

JavaScript test spies, stubs and mocks.

337 lines (300 loc) 10.5 kB
"use strict"; /** @module */ const arrayProto = require("@sinonjs/commons").prototypes.array; const calledInOrder = require("@sinonjs/commons").calledInOrder; const createMatcher = require("@sinonjs/samsam").createMatcher; const orderByFirstCall = require("@sinonjs/commons").orderByFirstCall; const timesInWords = require("./util/core/times-in-words"); const inspect = require("util").inspect; const stringSlice = require("@sinonjs/commons").prototypes.string.slice; const globalObject = require("@sinonjs/commons").global; const arraySlice = arrayProto.slice; const concat = arrayProto.concat; const forEach = arrayProto.forEach; const join = arrayProto.join; const splice = arrayProto.splice; function applyDefaults(obj, defaults) { for (const key of Object.keys(defaults)) { const val = obj[key]; if (val === null || typeof val === "undefined") { obj[key] = defaults[key]; } } } /** * @typedef {object} CreateAssertOptions * @global * * @property {boolean} [shouldLimitAssertionLogs] default is false * @property {number} [assertionLogLimit] default is 10K */ /** * Create an assertion object that exposes several methods to invoke * * @param {CreateAssertOptions} [opts] options bag * @returns {object} object with multiple assertion methods */ function createAssertObject(opts) { const cleanedAssertOptions = opts || {}; applyDefaults(cleanedAssertOptions, { shouldLimitAssertionLogs: false, assertionLogLimit: 1e4, }); const assert = { failException: "AssertError", fail: function fail(message) { let msg = message; if (cleanedAssertOptions.shouldLimitAssertionLogs) { msg = message.substring( 0, cleanedAssertOptions.assertionLogLimit, ); } const error = new Error(msg); error.name = this.failException || assert.failException; throw error; }, pass: function pass() { return; }, callOrder: function assertCallOrder() { verifyIsStub.apply(null, arguments); let expected = ""; let actual = ""; if (!calledInOrder(arguments)) { try { expected = join(arguments, ", "); const calls = arraySlice(arguments); let i = calls.length; while (i) { if (!calls[--i].called) { splice(calls, i, 1); } } actual = join(orderByFirstCall(calls), ", "); } catch (e) { // If this fails, we'll just fall back to the blank string } failAssertion( this, `expected ${expected} to be called in order but were called as ${actual}`, ); } else { assert.pass("callOrder"); } }, callCount: function assertCallCount(method, count) { verifyIsStub(method); let msg; if (typeof count !== "number") { msg = `expected ${inspect(count)} to be a number ` + `but was of type ${typeof count}`; failAssertion(this, msg); } else if (method.callCount !== count) { msg = `expected %n to be called ${timesInWords(count)} ` + `but was called %c%C`; failAssertion(this, method.printf(msg)); } else { assert.pass("callCount"); } }, expose: function expose(target, options) { if (!target) { throw new TypeError("target is null or undefined"); } const o = options || {}; const prefix = (typeof o.prefix === "undefined" && "assert") || o.prefix; const includeFail = typeof o.includeFail === "undefined" || Boolean(o.includeFail); const instance = this; forEach(Object.keys(instance), function (method) { if ( method !== "expose" && (includeFail || !/^(fail)/.test(method)) ) { target[exposedName(prefix, method)] = instance[method]; } }); return target; }, match: function match(actual, expectation) { const matcher = createMatcher(expectation); if (matcher.test(actual)) { assert.pass("match"); } else { const formatted = [ "expected value to match", ` expected = ${inspect(expectation)}`, ` actual = ${inspect(actual)}`, ]; failAssertion(this, join(formatted, "\n")); } }, }; function verifyIsStub() { const args = arraySlice(arguments); forEach(args, function (method) { if (!method) { assert.fail("fake is not a spy"); } if (method.proxy && method.proxy.isSinonProxy) { verifyIsStub(method.proxy); } else { if (typeof method !== "function") { assert.fail(`${method} is not a function`); } if (typeof method.getCall !== "function") { assert.fail(`${method} is not stubbed`); } } }); } function verifyIsValidAssertion(assertionMethod, assertionArgs) { switch (assertionMethod) { case "notCalled": case "called": case "calledOnce": case "calledTwice": case "calledThrice": if (assertionArgs.length !== 0) { assert.fail( `${assertionMethod} takes 1 argument but was called with ${ assertionArgs.length + 1 } arguments`, ); } break; default: break; } } function failAssertion(object, msg) { const obj = object || globalObject; const failMethod = obj.fail || assert.fail; failMethod.call(obj, msg); } function mirrorPropAsAssertion(name, method, message) { let msg = message; let meth = method; if (arguments.length === 2) { msg = method; meth = name; } assert[name] = function (fake) { verifyIsStub(fake); const args = arraySlice(arguments, 1); let failed = false; verifyIsValidAssertion(name, args); if (typeof meth === "function") { failed = !meth(fake); } else { failed = typeof fake[meth] === "function" ? !fake[meth].apply(fake, args) : !fake[meth]; } if (failed) { failAssertion( this, (fake.printf || fake.proxy.printf).apply( fake, concat([msg], args), ), ); } else { assert.pass(name); } }; } function exposedName(prefix, prop) { return !prefix || /^fail/.test(prop) ? prop : prefix + stringSlice(prop, 0, 1).toUpperCase() + stringSlice(prop, 1); } mirrorPropAsAssertion( "called", "expected %n to have been called at least once but was never called", ); mirrorPropAsAssertion( "notCalled", function (spy) { return !spy.called; }, "expected %n to not have been called but was called %c%C", ); mirrorPropAsAssertion( "calledOnce", "expected %n to be called once but was called %c%C", ); mirrorPropAsAssertion( "calledTwice", "expected %n to be called twice but was called %c%C", ); mirrorPropAsAssertion( "calledThrice", "expected %n to be called thrice but was called %c%C", ); mirrorPropAsAssertion( "calledOn", "expected %n to be called with %1 as this but was called with %t", ); mirrorPropAsAssertion( "alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t", ); mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new"); mirrorPropAsAssertion( "alwaysCalledWithNew", "expected %n to always be called with new", ); mirrorPropAsAssertion( "calledWith", "expected %n to be called with arguments %D", ); mirrorPropAsAssertion( "calledWithMatch", "expected %n to be called with match %D", ); mirrorPropAsAssertion( "alwaysCalledWith", "expected %n to always be called with arguments %D", ); mirrorPropAsAssertion( "alwaysCalledWithMatch", "expected %n to always be called with match %D", ); mirrorPropAsAssertion( "calledWithExactly", "expected %n to be called with exact arguments %D", ); mirrorPropAsAssertion( "calledOnceWithExactly", "expected %n to be called once and with exact arguments %D", ); mirrorPropAsAssertion( "calledOnceWithMatch", "expected %n to be called once and with match %D", ); mirrorPropAsAssertion( "alwaysCalledWithExactly", "expected %n to always be called with exact arguments %D", ); mirrorPropAsAssertion( "neverCalledWith", "expected %n to never be called with arguments %*%C", ); mirrorPropAsAssertion( "neverCalledWithMatch", "expected %n to never be called with match %*%C", ); mirrorPropAsAssertion("threw", "%n did not throw exception%C"); mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C"); return assert; } module.exports = createAssertObject(); module.exports.createAssertObject = createAssertObject;