| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- /* eslint-disable jsdoc/require-jsdoc */
- import {expect} from 'chai';
- import {createSpy} from './create-spy.js';
- describe('createSpy', function () {
- describe('argument validation', function () {
- it('should allow to create spy without arguments', function () {
- const spy = createSpy();
- expect(spy).to.be.a('function');
- const res = spy();
- expect(res).to.be.undefined;
- expect(spy.calls).to.have.length(1);
- });
- it('should throw when trying to spy on null', function () {
- expect(() => createSpy(null)).to.throw(
- TypeError,
- 'Attempted to spy on null.',
- );
- });
- it('should throw if target is not a function and no method name is given', function () {
- expect(() => createSpy({})).to.throw(
- TypeError,
- 'Attempted to spy on a non-function value. To spy on an object method, ' +
- 'you must provide the method name as the second argument.',
- );
- expect(() => createSpy(123)).to.throw(
- TypeError,
- 'Attempted to spy on a non-function value. To spy on an object method, ' +
- 'you must provide the method name as the second argument.',
- );
- });
- it('should throw if custom implementation for a function spy is not a function', function () {
- const targetFn = () => {};
- expect(() => createSpy(targetFn, 'not a function')).to.throw(
- TypeError,
- 'When spying on a function, the second argument (custom implementation) must be a function if provided.',
- );
- });
- it('should throw if trying to spy on a non-existent method', function () {
- const obj = {};
- expect(() => createSpy(obj, 'nonExistentMethod')).to.throw(
- TypeError,
- 'Attempted to spy on a non-existent property: "nonExistentMethod"',
- );
- });
- it('should throw if trying to spy on a non-function property of an object', function () {
- const obj = {prop: 123};
- expect(() => createSpy(obj, 'prop')).to.throw(
- TypeError,
- 'Attempted to spy on "prop" which is not a function. It is a "number".',
- );
- });
- it('should throw if custom implementation for a method spy is not a function', function () {
- const obj = {method: () => {}};
- expect(() => createSpy(obj, 'method', 'not a function')).to.throw(
- TypeError,
- 'When spying on a method, the third argument (custom implementation) must be a function if provided.',
- );
- });
- });
- describe('when spying on a standalone function', function () {
- it('should return a function that is the spy', function () {
- const targetFn = () => {};
- const spy = createSpy(targetFn);
- expect(spy).to.be.a('function');
- });
- it('should not be called initially', function () {
- const spy = createSpy(function () {});
- expect(spy.called).to.be.false;
- expect(spy.callCount).to.be.eq(0);
- });
- it('should track calls and arguments', function () {
- const sum = (a, b) => a + b;
- const spy = createSpy(sum);
- spy(1, 2);
- spy(3, 4);
- expect(spy.called).to.be.true;
- expect(spy.callCount).to.be.eq(2);
- expect(spy.calls[0].args).to.deep.equal([1, 2]);
- expect(spy.calls[1].args).to.deep.equal([3, 4]);
- });
- it('should call the original function and return its value by default', function () {
- const originalFn = () => 'original value';
- const spy = createSpy(originalFn);
- const result = spy();
- expect(result).to.be.eq('original value');
- expect(spy.called).to.be.true;
- });
- it('should use the custom implementation if provided', function () {
- const originalFn = () => 'original';
- const customImpl = () => 'custom';
- const spy = createSpy(originalFn, customImpl);
- const result = spy();
- expect(result).to.be.eq('custom');
- expect(spy.called).to.be.true;
- });
- it('should preserve `this` context for the original function', function () {
- const contextObj = {val: 10};
- function originalFn() {
- return this.val;
- }
- const spy = createSpy(originalFn);
- const result = spy.call(contextObj);
- expect(result).to.be.eq(10);
- expect(spy.calls[0].thisArg).to.be.eq(contextObj);
- });
- it('should preserve `this` context for the custom implementation', function () {
- const contextObj = {val: 20};
- const originalFn = () => {};
- function customImpl() {
- return this.val;
- }
- const spy = createSpy(originalFn, customImpl);
- const result = spy.call(contextObj);
- expect(result).to.be.eq(20);
- expect(spy.calls[0].thisArg).to.be.eq(contextObj);
- });
- describe('.restore()', function () {
- it('should reset its history and not throw', function () {
- const standaloneFn = () => 'standalone result';
- const fnSpy = createSpy(standaloneFn);
- fnSpy('call standalone');
- expect(fnSpy.called).to.be.true;
- expect(fnSpy.callCount).to.be.eq(1);
- expect(() => fnSpy.restore()).to.not.throw();
- expect(fnSpy.callCount).to.be.eq(0);
- expect(fnSpy.called).to.be.false;
- });
- });
- });
- describe('when spying on an object method', function () {
- it('should replace the original method with the spy', function () {
- const obj = {method: () => {}};
- const spy = createSpy(obj, 'method');
- expect(obj.method).to.be.eq(spy);
- expect(obj.method).to.be.a('function');
- });
- it('should call the original method with object context and return its value', function () {
- const originalMethodImpl = function (val) {
- return `original: ${this.name} ${val}`;
- };
- const obj = {
- name: 'TestObj',
- method: originalMethodImpl,
- };
- const spy = createSpy(obj, 'method');
- const result = obj.method('arg1');
- expect(result).to.be.eq('original: TestObj arg1');
- expect(spy.callCount).to.be.eq(1);
- expect(spy.calls[0].thisArg).to.be.eq(obj);
- expect(spy.calls[0].args).to.deep.equal(['arg1']);
- });
- it('should use custom implementation with object context if provided', function () {
- const obj = {
- name: 'TestObj',
- method: () => {},
- };
- const customImpl = function (val) {
- return `custom: ${this.name} ${val}`;
- };
- const spy = createSpy(obj, 'method', customImpl);
- const result = obj.method('argCustom');
- expect(result).to.be.eq('custom: TestObj argCustom');
- expect(spy.callCount).to.be.eq(1);
- expect(spy.calls[0].thisArg).to.be.eq(obj);
- });
- describe('.restore()', function () {
- it('should put the original method back and reset spy history', function () {
- const originalMethodImpl = function (val) {
- return `original: ${this.name} ${val}`;
- };
- const obj = {
- name: 'TestObj',
- method: originalMethodImpl,
- };
- const spy = createSpy(obj, 'method');
- obj.method('call before restore');
- expect(spy.called).to.be.true;
- expect(obj.method).to.be.eq(spy);
- spy.restore();
- expect(obj.method).to.be.eq(originalMethodImpl);
- const result = obj.method('call after restore');
- expect(result).to.be.eq('original: TestObj call after restore');
- expect(spy.callCount).to.be.eq(0);
- expect(spy.called).to.be.false;
- });
- it('should restore an "own" property by assigning the original value back', function () {
- const obj = {
- fn: function () {
- return 'own';
- },
- };
- const spy = createSpy(obj, 'fn');
- expect(obj.fn).to.be.eq(spy);
- spy.restore();
- expect(obj.fn()).to.be.eq('own');
- expect(Object.prototype.hasOwnProperty.call(obj, 'fn')).to.be.true;
- });
- it('should restore a prototype method by deleting the spy from the instance', function () {
- class Base {
- method() {
- return 'prototype';
- }
- }
- const instance = new Base();
- expect(Object.prototype.hasOwnProperty.call(instance, 'method')).to.be
- .false;
- const spy = createSpy(instance, 'method');
- expect(Object.prototype.hasOwnProperty.call(instance, 'method')).to.be
- .true;
- expect(instance.method).to.be.eq(spy);
- spy.restore();
- expect(Object.prototype.hasOwnProperty.call(instance, 'method')).to.be
- .false;
- expect(instance.method()).to.be.eq('prototype');
- });
- });
- });
- describe('spy properties and methods', function () {
- it('should have the __isSpy property with true value', function () {
- const spy = createSpy(() => {});
- expect(spy['__isSpy']).to.be.true;
- });
- describe('.calls', function () {
- it('should return an array of CallInfo', function () {
- const targetFn = (a, b) => a + b;
- const spy = createSpy(targetFn);
- expect(spy.calls).to.be.eql([]);
- spy(1, 2);
- expect(spy.calls[0]).to.be.eql({
- args: [1, 2],
- thisArg: undefined,
- returnValue: 3,
- error: undefined,
- });
- spy(5, 3);
- expect(spy.calls[1]).to.be.eql({
- args: [5, 3],
- thisArg: undefined,
- returnValue: 8,
- error: undefined,
- });
- expect(spy.calls).to.have.length(2);
- });
- });
- describe('.callCount and .called', function () {
- it('should have callCount = 0 and called = false initially', function () {
- const spy = createSpy(() => {});
- expect(spy.callCount).to.be.eq(0);
- expect(spy.called).to.be.false;
- });
- it('should update after calls', function () {
- const spy = createSpy(() => {});
- spy(1, 1);
- expect(spy.callCount).to.be.eq(1);
- expect(spy.called).to.be.true;
- spy(2, 2);
- expect(spy.callCount).to.be.eq(2);
- });
- });
- });
- });
|