'use strict'; import * as testHelpers from './testHelpers'; import {AssertionError} from 'assert'; import {DeepPartial} from '../../types/util'; import type sinon from 'sinon'; import util from 'util'; import 'should'; /** * Asserts that a stub or apy called specified number of times with args matched specified ones * @param {Function} spy Sinon stub or spy * @param {Number} calls Expected number of calls * @param {Array} args Expected call arguments * @throws {AssertionError} If assertion failed */ export function callCountWithMatch( spy: Spy, calls: number, ...args: DeepPartial> ) { if (!(spy as any).isSinonProxy) { throw new AssertionError({message: 'Given function is not a sinon spy'}); } let matchedCalls = testHelpers.getCallsWithMatch(spy, ...args); if (matchedCalls.length !== calls) { throw Object.assign( new AssertionError({message: `Spy expected to be called ${calls} times with specified arguments but was called ` + `${matchedCalls.length} times with them of ${spy.callCount} total calls`}), {callArgs: util.inspect(spy.args, {depth: 3})} ); } } /** * Asserts that a stub or apy called specified number of times with args that deep equal to specified ones * @param {Function} spy Sinon stub or spy * @param {Number} calls Expected number of calls * @param {Array} args Expected call arguments * @throws {AssertionError} If assertion failed */ export function callCountWithExactly(spy: Spy, calls: number, ...args: Parameters) { if (!(spy as any).isSinonProxy) { throw new AssertionError({message: 'Given function is not a sinon spy'}); } let matchedCalls = testHelpers.getCallsWithExactly(spy, ...args as DeepPartial>); matchedCalls.length.should.equal(calls, `Spy expected to be called ${calls} times with specified arguments ` + `but was called ${matchedCalls.length} times with them of ${spy.callCount} total calls`); } /** * Asserts that array matches to expected one with and the lengths are equal * @param {Array} actualArray Actual array to match * @param {Array} expectedArray Expected array to match to * @throws {AssertionError} If assertion failed */ export function arrayMatchWithEqualLength(actualArray, expectedArray) { actualArray.should.match(expectedArray); actualArray.length.should.equal(expectedArray.length); } /** Spy call options */ export type SpyCall = { /** Sinon spy */ spy: sinon.SinonSpy, /** Call index */ call: number, /** If specified, asserts args match */ matchArgs?: DeepPartial, /** Logging label. Defaults to `spy` function name, which defaults to `default` */ label?: string }; /** * Sinon's callOrder seems to have some bug giving incorrect assertion, so this is a manual implementation * @param spyCalls spy calls * @throws assertion error */ export function callOrder(spyCalls: SpyCall[]) { type CallId = {id: number, label: string}; let callIds: CallId[] = spyCalls.map((call, index) => { const label = call.label || call.spy.name || 'default'; let sinonCall = call.spy.getCall(call.call); if (!sinonCall) { throw new AssertionError({message: `Call ${index} (${label}) does not exist`}); } return {id: (sinonCall as any).callId, label}; }); let previousCallId: CallId; for (let index = 0; index < spyCalls.length; ++index) { if (previousCallId !== undefined && callIds[index].id < previousCallId.id) { throw new AssertionError({ message: `Wrong call order detected in call ids ${callIds.map(id => `\n${id.id} (${id.label})`)}`, expected: `Call id larger than the previous call ${previousCallId}`, actual: callIds[index] }); } if (spyCalls[index].matchArgs) { try { spyCalls[index].spy.getCall(spyCalls[index].call).args.should.match(spyCalls[index].matchArgs); } catch (err) { throw Object.assign( new AssertionError({message: `Call ${index} (${callIds[index].label}) args do not match`}), {index, label: callIds[index].label, cause: err} ); } } previousCallId = callIds[index]; } }