Browse Source

refactor: simplifies chai plugin modifiers

e22m4u 2 days ago
parent
commit
82fa492bce

+ 87 - 0
README.md

@@ -224,6 +224,93 @@ console.log(loggerSpy.called);
 // false (история сброшена)
 // false (история сброшена)
 ```
 ```
 
 
+## Плагин для Chai
+
+Плагин интегрируется в библиотеку [chai](https://www.npmjs.com/package/chai)
+и предоставляет набор утверждений для проверки состояния шпионов. Расширение
+позволяет проверять факты вызова функций, количество вызовов, переданные
+аргументы и порядок выполнения.
+
+Подключение плагина выполняется через метод `use`.
+
+```js
+import chai from 'chai';
+import {chaiSpiesPlugin} from '@e22m4u/js-spy';
+
+chai.use(chaiSpiesPlugin);
+```
+
+Базовый пример:
+
+```js
+// проверка, является ли объект шпионом
+expect(fn).to.be.spy;
+expect(obj).to.not.be.spy;
+
+// проверка, что шпион был вызван хотя бы раз
+expect(spy).to.be.called();
+
+// проверка отсутствия вызовов
+expect(spy).to.not.be.called();
+```
+
+Количество вызовов проверяется с помощью специальных модификаторов.
+Доступны методы для точного совпадения, а также для проверки
+минимального и максимального количества раз.
+
+Модификаторы:
+
+- `once`: один раз;
+- `twice`: два раза;
+- `exactly(n)`: точно n раз;
+- `min(n)` или `at.least(n)`: минимум n раз;
+- `max(n)` или `at.most(n)`: максимум n раз;
+- `above(n)` или `gt(n)`: больше n раз;
+- `below(n)` или `lt(n)`: меньше n раз;
+
+```js
+// проверка точного количества вызовов
+expect(spy).to.have.been.called.once;
+expect(spy).to.have.been.called.twice;
+expect(spy).to.have.been.called.exactly(3);
+
+// проверка диапазона вызовов
+expect(spy).to.have.been.called.min(1);
+expect(spy).to.have.been.called.at.most(5);
+expect(spy).to.have.been.called.above(0);
+```
+
+Проверка аргументов осуществляется методом `with`. Сравнение
+производится строго: количество и значения аргументов должны совпадать.
+Цепочка `always` позволяет утверждать, что все вызовы шпиона
+соответствовали условию.
+
+```js
+// проверка, что шпион был вызван с аргументами 1 и 'foo'
+expect(spy).to.have.been.called.with(1, 'foo');
+
+// проверка, что каждый вызов содержал указанные аргументы
+expect(spy).to.have.been.called.always.with(true);
+```
+
+Доступна проверка аргументов для конкретного порядкового номера вызова.
+Для этого используются свойства `first`, `second`, `third` или метод
+`nth(index)`.
+
+```js
+// проверка аргументов первого вызова
+expect(spy).to.have.been.called.first.with('start');
+
+// проверка аргументов второго вызова
+expect(spy).to.have.been.called.second.with('process');
+
+// проверка аргументов третьего вызова
+expect(spy).to.have.been.called.third.with('end');
+
+// проверка аргументов десятого вызова
+expect(spy).on.nth(10).be.called.with('result');
+```
+
 ## Справочник API
 ## Справочник API
 
 
 ### Функция `createSpy`
 ### Функция `createSpy`

+ 2 - 2
package.json

@@ -31,8 +31,8 @@
     "lint": "tsc && eslint ./src",
     "lint": "tsc && eslint ./src",
     "lint:fix": "tsc && eslint ./src --fix",
     "lint:fix": "tsc && eslint ./src --fix",
     "format": "prettier --write \"./src/**/*.js\"",
     "format": "prettier --write \"./src/**/*.js\"",
-    "test": "npm run lint && c8 --reporter=text-summary mocha",
-    "test:coverage": "npm run lint && c8 --reporter=text mocha",
+    "test": "npm run lint && c8 --reporter=text-summary mocha --bail",
+    "test:coverage": "npm run lint && c8 --reporter=text mocha --bail",
     "build:cjs": "rimraf ./dist/cjs && node build-cjs.js",
     "build:cjs": "rimraf ./dist/cjs && node build-cjs.js",
     "prepare": "husky"
     "prepare": "husky"
   },
   },

+ 13 - 95
src/chai/chai-spies-plugin.d.ts

@@ -1,4 +1,3 @@
-
 declare global {
 declare global {
   namespace Chai {
   namespace Chai {
     interface LanguageChains {
     interface LanguageChains {
@@ -27,11 +26,11 @@ declare global {
       called: ChaiSpies.Called;
       called: ChaiSpies.Called;
 
 
       /**
       /**
-       *  * ####.been
-       * * Assert that something has been spied on. Negation passes through.
-       * * ```ts
-       * * expect(spy).to.have.been.called();
-       * * spy.should.have.been.called();
+       * ####.been
+       * Assert that something has been spied on. Negation passes through.
+       * ```ts
+       * expect(spy).to.have.been.called();
+       * spy.should.have.been.called();
        * ```
        * ```
        * Note that ```been``` can be used as a chainable method.
        * Note that ```been``` can be used as a chainable method.
        */
        */
@@ -201,58 +200,13 @@ declare global {
       interface With {
       interface With {
         /**
         /**
          * ####.with
          * ####.with
-         * Assert that a spy has been called with a given argument at least once, even if more arguments were provided.
-         * ```ts
-         * spy('foo');
-         * expect(spy).to.have.been.called.with('foo');
-         * spy.should.have.been.called.with('foo');
-         * ```
-         * Will also pass for ```spy('foo', 'bar')``` and ```spy(); spy('foo')```.
-         * If used with multiple arguments, assert that a spy has been called with all the given arguments at least once.
-         * ```ts
-         * spy('foo', 'bar', 1);
-         * expect(spy).to.have.been.called.with('bar', 'foo');
-         * spy.should.have.been.called.with('bar', 'foo');
-         * ```
-         */
-        (
-          a: any,
-          b?: any,
-          c?: any,
-          d?: any,
-          e?: any,
-          f?: any,
-          g?: any,
-          h?: any,
-          i?: any,
-          j?: any,
-        ): Chai.Assertion;
-
-        /**
-         * ####.with.exactly
-         * Similar to .with, but will pass only if the list of arguments is exactly the same as the one provided.
+         * Assert that a spy has been called with the given arguments.
          * ```ts
          * ```ts
-         * spy();
          * spy('foo', 'bar');
          * spy('foo', 'bar');
-         * expect(spy).to.have.been.called.with.exactly('foo', 'bar');
-         * spy.should.have.been.called.with.exactly('foo', 'bar');
+         * expect(spy).to.have.been.called.with('foo', 'bar');
          * ```
          * ```
-         * Will not pass for ```spy('foo')```, ```spy('bar')```, ```spy('bar'); spy('foo')```, ```spy('foo'); spy('bar')```, ```spy('bar', 'foo')``` or ```spy('foo', 'bar', 1)```.
-         * Can be used for calls with a single argument too.
          */
          */
-
-        exactly(
-          a?: any,
-          b?: any,
-          c?: any,
-          d?: any,
-          e?: any,
-          f?: any,
-          g?: any,
-          h?: any,
-          i?: any,
-          j?: any,
-        ): Chai.Assertion;
+        (...args: any[]): Chai.Assertion;
       }
       }
 
 
       interface Always {
       interface Always {
@@ -262,50 +216,14 @@ declare global {
       interface AlwaysWith {
       interface AlwaysWith {
         /**
         /**
          * ####.always.with
          * ####.always.with
-         * Assert that every time the spy has been called the argument list contained the given arguments.
+         * Assert that every time the spy has been called the argument list strictly matched the given arguments (order and value).
          * ```ts
          * ```ts
-         * spy('foo');
          * spy('foo', 'bar');
          * spy('foo', 'bar');
-         * spy(1, 2, 'foo');
-         * expect(spy).to.have.been.called.always.with('foo');
-         * spy.should.have.been.called.always.with('foo');
-         * ```
-         */
-        (
-          a: any,
-          b?: any,
-          c?: any,
-          d?: any,
-          e?: any,
-          f?: any,
-          g?: any,
-          h?: any,
-          i?: any,
-          j?: any,
-        ): Chai.Assertion;
-
-        /**
-         * ####.always.with.exactly
-         * Assert that the spy has never been called with a different list of arguments than the one provided.
-         * ```ts
-         * spy('foo');
-         * spy('foo');
-         * expect(spy).to.have.been.called.always.with.exactly('foo');
-         * spy.should.have.been.called.always.with.exactly('foo');
+         * spy('foo', 'bar', 'baz');
+         * expect(spy).to.have.been.called.always.with('foo', 'bar');
          * ```
          * ```
          */
          */
-        exactly(
-          a?: any,
-          b?: any,
-          c?: any,
-          d?: any,
-          e?: any,
-          f?: any,
-          g?: any,
-          h?: any,
-          i?: any,
-          j?: any,
-        ): Chai.Assertion;
+        (...args: any[]): Chai.Assertion;
       }
       }
 
 
       interface At {
       interface At {
@@ -340,4 +258,4 @@ declare global {
 /**
 /**
  * Chai spies plugin.
  * Chai spies plugin.
  */
  */
-export declare const chaiSpiesPlugin: any;
+export declare const chaiSpiesPlugin: any;

+ 28 - 76
src/chai/chai-spies-plugin.js

@@ -20,9 +20,9 @@ export function chaiSpiesPlugin(chai, _) {
 
 
   function assertCalled (n) {
   function assertCalled (n) {
     new Assertion(this._obj).to.be.spy;
     new Assertion(this._obj).to.be.spy;
-    var spy = this._obj;
+    const spy = this._obj;
 
 
-    if (n) {
+    if (n != undefined) {
       this.assert(
       this.assert(
           spy.calls.length === n
           spy.calls.length === n
         , 'expected ' + this._obj + ' to have been called #{exp} but got #{act}'
         , 'expected ' + this._obj + ' to have been called #{exp} but got #{act}'
@@ -68,28 +68,23 @@ export function chaiSpiesPlugin(chai, _) {
 
 
   function nthCallWith(spy, n, expArgs) {
   function nthCallWith(spy, n, expArgs) {
     if (spy.calls.length <= n) return false;
     if (spy.calls.length <= n) return false;
-
-    var actArgs = spy.calls[n].args.slice()
-      , passed = 0;
-
-    expArgs.forEach(function (expArg) {
-      for (var i = 0; i < actArgs.length; i++) {
-        if (_.eql(actArgs[i], expArg)) {
-          passed++;
-          actArgs.splice(i, 1);
-          break;
-        }
+    const actArgs = spy.calls[n].args;
+    if (actArgs.length !== expArgs.length) return false;
+    // проверка каждого ожидаемого аргумента на строгое
+    // соответствие позиции и значения
+    for (let i = 0; i < expArgs.length; i++) {
+      if (!_.eql(actArgs[i], expArgs[i])) {
+        return false;
       }
       }
-    });
-
-    return passed === expArgs.length;
+    }
+    return true;
   }
   }
 
 
   function numberOfCallsWith(spy, expArgs) {
   function numberOfCallsWith(spy, expArgs) {
-    var found = 0
-      , calls = spy.calls;
+    let found = 0;
+    const calls = spy.calls;
 
 
-    for (var i = 0; i < calls.length; i++) {
+    for (let i = 0; i < calls.length; i++) {
       if (nthCallWith(spy, i, expArgs)) {
       if (nthCallWith(spy, i, expArgs)) {
         found++;
         found++;
       }
       }
@@ -133,22 +128,24 @@ export function chaiSpiesPlugin(chai, _) {
 
 
   function assertWith() {
   function assertWith() {
     new Assertion(this._obj).to.be.spy;
     new Assertion(this._obj).to.be.spy;
-    var expArgs = [].slice.call(arguments, 0)
+    const expArgs = [].slice.call(arguments, 0)
       , spy = this._obj
       , spy = this._obj
       , calls = spy.calls
       , calls = spy.calls
       , always = _.flag(this, 'spy always')
       , always = _.flag(this, 'spy always')
       , nthCall = _.flag(this, 'spy nth call with');
       , nthCall = _.flag(this, 'spy nth call with');
 
 
     if (always) {
     if (always) {
-      var passed = numberOfCallsWith(spy, expArgs);
+      const passed = numberOfCallsWith(spy, expArgs);
       this.assert(
       this.assert(
-          calls.length > 0 && passed === calls.length
+          arguments.length
+            ? calls.length && passed === calls.length
+            : calls.length === 0
         , 'expected ' + this._obj + ' to have been always called with #{exp} but got ' + passed + ' out of ' + calls.length
         , 'expected ' + this._obj + ' to have been always called with #{exp} but got ' + passed + ' out of ' + calls.length
         , 'expected ' + this._obj + ' to have not always been called with #{exp}'
         , 'expected ' + this._obj + ' to have not always been called with #{exp}'
         , expArgs
         , expArgs
       );
       );
     } else if (nthCall) {
     } else if (nthCall) {
-      var ordinalNumber = generateOrdinalNumber(nthCall),
+      const ordinalNumber = generateOrdinalNumber(nthCall),
           actArgs = calls[nthCall - 1];
           actArgs = calls[nthCall - 1];
       new Assertion(this._obj).to.be.have.been.called.min(nthCall);
       new Assertion(this._obj).to.be.have.been.called.min(nthCall);
       this.assert(
       this.assert(
@@ -185,59 +182,14 @@ export function chaiSpiesPlugin(chai, _) {
 
 
   Assertion.addMethod('exactly', function () {
   Assertion.addMethod('exactly', function () {
     new Assertion(this._obj).to.be.spy;
     new Assertion(this._obj).to.be.spy;
-    var always = _.flag(this, 'spy always')
-      , _with = _.flag(this, 'spy with')
-      , args = [].slice.call(arguments, 0)
-      , calls = this._obj.calls
-      , nthCall = _.flag(this, 'spy nth call with')
-      , passed;
-
-    if (always && _with) {
-      passed = 0
-      calls.forEach(function (call) {
-        if (call.args.length !== args.length) return;
-        if (_.eql(call.args, args)) passed++;
-      });
-
-      this.assert(
-          calls.length > 0 && passed === calls.length
-        , 'expected ' + this._obj + ' to have been always called with exactly #{exp} but got ' + passed + ' out of ' + calls.length
-        , 'expected ' + this._obj + ' to have not always been called with exactly #{exp}'
-        , args
-      );
-    } else if(_with && nthCall) {
-      var ordinalNumber = generateOrdinalNumber(nthCall),
-          actArgs = calls[nthCall - 1];
-      new Assertion(this._obj).to.be.have.been.called.min(nthCall);
-      this.assert(
-          _.eql(actArgs, args)
-        , 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with exactly #{exp} but got #{act}'
-        , 'expected ' + this._obj + ' to have not been called at the ' + ordinalNumber + ' time with exactly #{exp}'
-        , args
-        , actArgs
-      );
-    } else if (_with) {
-      passed = 0;
-      calls.forEach(function (call) {
-        if (call.args.length !== args.length) return;
-        if (_.eql(call.args, args)) passed++;
-      });
-
-      this.assert(
-          passed > 0
-        , 'expected ' + this._obj + ' to have been called with exactly #{exp}'
-        , 'expected ' + this._obj + ' to not have been called with exactly #{exp} but got ' + passed + ' times'
-        , args
-      );
-    } else {
-      this.assert(
-          this._obj.calls.length === args[0]
-        , 'expected ' + this._obj + ' to have been called #{exp} times but got #{act}'
-        , 'expected ' + this._obj + ' to not have been called #{exp} times'
-        , args[0]
-        , this._obj.calls.length
-      );
-    }
+    const args = [].slice.call(arguments, 0);
+    this.assert(
+        this._obj.calls.length === args[0]
+      , 'expected ' + this._obj + ' to have been called #{exp} times but got #{act}'
+      , 'expected ' + this._obj + ' to not have been called #{exp} times'
+      , args[0]
+      , this._obj.calls.length
+    );
   });
   });
 
 
   function above (_super) {
   function above (_super) {

+ 55 - 64
src/chai/chai-spies-plugin.spec.js

@@ -11,13 +11,13 @@ describe('chaiSpiesPlugin', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
     expect(spy).to.be.spy;
     expect(spy).to.be.spy;
-    expect({}).not.to.be.spy;
+    expect({}).to.not.be.spy;
   });
   });
 
 
   it('should assert that a spy has been called', function () {
   it('should assert that a spy has been called', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.not.called();
+    expect(spy).to.not.have.been.called();
     spy();
     spy();
     expect(spy).to.have.been.called();
     expect(spy).to.have.been.called();
   });
   });
@@ -34,43 +34,51 @@ describe('chaiSpiesPlugin', function () {
   it('should assert that a spy has been called exactly once', function () {
   it('should assert that a spy has been called exactly once', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.called.not.once;
+    expect(spy).to.not.have.been.called.once;
     spy();
     spy();
     expect(spy).to.have.been.called.once;
     expect(spy).to.have.been.called.once;
     spy();
     spy();
-    expect(spy).to.have.been.called.not.once;
+    expect(spy).to.not.have.been.called.once;
   });
   });
 
 
   it('should assert that a spy has been called exactly twice', function () {
   it('should assert that a spy has been called exactly twice', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.called.not.twice;
+    expect(spy).to.not.have.been.called.twice;
     spy();
     spy();
-    expect(spy).to.have.been.called.not.twice;
+    expect(spy).to.not.have.been.called.twice;
     spy();
     spy();
     expect(spy).to.have.been.called.twice;
     expect(spy).to.have.been.called.twice;
     spy();
     spy();
-    expect(spy).to.have.been.called.not.twice;
+    expect(spy).to.not.have.been.called.twice;
+  });
+
+  it('should assert that a spy has been called exactly 0 times', function () {
+    const fn = () => undefined;
+    const spy = createSpy(fn);
+    expect(spy).to.have.been.called.exactly(0);
+    spy();
+    expect(spy).to.not.have.been.called.exactly(0);
   });
   });
 
 
   it('should assert that a spy has been called exactly *n times', function () {
   it('should assert that a spy has been called exactly *n times', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.called.not.exactly(2);
+    expect(spy).to.not.have.been.called.exactly(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.exactly(2);
+    expect(spy).to.not.have.been.called.exactly(2);
     spy();
     spy();
     expect(spy).to.have.been.called.exactly(2);
     expect(spy).to.have.been.called.exactly(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.exactly(2);
+    expect(spy).to.not.have.been.called.exactly(2);
   });
   });
 
 
   it('should assert that a spy has been called minimum of *n times', function () {
   it('should assert that a spy has been called minimum of *n times', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.called.not.min(2);
+    expect(spy).to.not.have.been.called.min(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.min(2);
+    expect(spy).to.not.have.been.called.min(2);
     spy();
     spy();
     expect(spy).to.have.been.called.min(2);
     expect(spy).to.have.been.called.min(2);
     spy();
     spy();
@@ -86,17 +94,17 @@ describe('chaiSpiesPlugin', function () {
     spy();
     spy();
     expect(spy).to.have.been.called.max(2);
     expect(spy).to.have.been.called.max(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.max(2);
+    expect(spy).to.not.have.been.called.max(2);
   });
   });
 
 
   it('should assert that a spy has been called more than *n times', function () {
   it('should assert that a spy has been called more than *n times', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.called.not.above(2);
+    expect(spy).to.not.have.been.called.above(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.above(2);
+    expect(spy).to.not.have.been.called.above(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.above(2);
+    expect(spy).to.not.have.been.called.above(2);
     spy();
     spy();
     expect(spy).to.have.been.called.above(2);
     expect(spy).to.have.been.called.above(2);
   });
   });
@@ -108,19 +116,19 @@ describe('chaiSpiesPlugin', function () {
     spy();
     spy();
     expect(spy).to.have.been.called.below(2);
     expect(spy).to.have.been.called.below(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.below(2);
+    expect(spy).to.not.have.been.called.below(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.below(2);
+    expect(spy).to.not.have.been.called.below(2);
   });
   });
 
 
   it('should assert that a spy has been called greater than *n times', function () {
   it('should assert that a spy has been called greater than *n times', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.called.not.gt(2);
+    expect(spy).to.not.have.been.called.gt(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.gt(2);
+    expect(spy).to.not.have.been.called.gt(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.gt(2);
+    expect(spy).to.not.have.been.called.gt(2);
     spy();
     spy();
     expect(spy).to.have.been.called.gt(2);
     expect(spy).to.have.been.called.gt(2);
   });
   });
@@ -132,15 +140,15 @@ describe('chaiSpiesPlugin', function () {
     spy();
     spy();
     expect(spy).to.have.been.called.lt(2);
     expect(spy).to.have.been.called.lt(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.lt(2);
+    expect(spy).to.not.have.been.called.lt(2);
     spy();
     spy();
-    expect(spy).to.have.been.called.not.lt(2);
+    expect(spy).to.not.have.been.called.lt(2);
   });
   });
 
 
   it('should assert that a spy has been called first', function () {
   it('should assert that a spy has been called first', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.not.called.first;
+    expect(spy).to.not.have.been.called.first;
     spy();
     spy();
     expect(spy).to.have.been.called.first;
     expect(spy).to.have.been.called.first;
     spy();
     spy();
@@ -150,9 +158,9 @@ describe('chaiSpiesPlugin', function () {
   it('should assert that a spy has been called second', function () {
   it('should assert that a spy has been called second', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.not.called.second;
+    expect(spy).to.not.have.been.called.second;
     spy();
     spy();
-    expect(spy).to.have.been.not.called.second;
+    expect(spy).to.not.have.been.called.second;
     spy();
     spy();
     expect(spy).to.have.been.called.second;
     expect(spy).to.have.been.called.second;
   });
   });
@@ -160,59 +168,42 @@ describe('chaiSpiesPlugin', function () {
   it('should assert that a spy has been called third', function () {
   it('should assert that a spy has been called third', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.not.called.third;
+    expect(spy).to.not.have.been.called.third;
     spy();
     spy();
-    expect(spy).to.have.been.not.called.third;
+    expect(spy).to.not.have.been.called.third;
     spy();
     spy();
-    expect(spy).to.have.been.not.called.third;
+    expect(spy).to.not.have.been.called.third;
     spy();
     spy();
     expect(spy).to.have.been.called.third;
     expect(spy).to.have.been.called.third;
   });
   });
 
 
-  it('should assert that a spy has been called with given arguments at least once, even if more arguments were provided', function () {
+  it('should assert that a spy has been called with given arguments and match its order', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.not.called.with(1, 2);
-    spy();
-    expect(spy).to.have.been.not.called.with(1, 2);
+    expect(spy).to.not.have.been.called.with(1);
     spy(1, 2, 3);
     spy(1, 2, 3);
-    expect(spy).to.have.been.called.with(1, 2);
+    expect(spy).to.not.have.been.called.with(1);
+    expect(spy).to.not.have.been.called.with(1, 2);
     expect(spy).to.have.been.called.with(1, 2, 3);
     expect(spy).to.have.been.called.with(1, 2, 3);
-    expect(spy).to.have.been.not.called.with(1, 2, 3, 4);
-  });
-
-  it('should assert that a spy has been called exactly with given arguments at least once', function () {
-    const fn = () => undefined;
-    const spy = createSpy(fn);
-    expect(spy).to.have.been.not.called.with.exactly(1, 2);
-    spy();
-    expect(spy).to.have.been.not.called.with.exactly(1, 2);
-    spy(1, 2, 3);
-    expect(spy).to.have.been.not.called.with.exactly(1, 2);
-    expect(spy).to.have.been.called.with.exactly(1, 2, 3);
-    expect(spy).to.have.been.not.called.with.exactly(1, 2, 3, 4);
-  });
-
-  it('should assert that every time the spy has been called the argument list contained the given arguments', function () {
-    const fn = () => undefined;
-    const spy = createSpy(fn);
-    expect(spy).to.have.been.not.called.always.with(1, 2);
-    spy(1, 2);
-    expect(spy).to.have.been.called.always.with(1, 2);
-    spy(1, 2, 3);
-    expect(spy).to.have.been.called.always.with(1, 2);
-    expect(spy).to.have.been.not.called.always.with(1, 2, 3);
+    expect(spy).to.not.have.been.called.with(2, 1);
+    expect(spy).to.not.have.been.called.with(1, 3);
+    expect(spy).to.not.have.been.called.with(3, 2, 1);
+    expect(spy).to.not.have.been.called.with(1, 2, 3, 4);
   });
   });
 
 
-  it('should assert that the spy has never been called with a different list of arguments than the one provided', function () {
+  it('should assert that every time the spy has been called with given arguments', function () {
     const fn = () => undefined;
     const fn = () => undefined;
     const spy = createSpy(fn);
     const spy = createSpy(fn);
-    expect(spy).to.have.been.not.called.always.with.exactly(1, 2);
-    spy(1, 2);
-    expect(spy).to.have.been.called.always.with.exactly(1, 2);
+    expect(spy).to.not.have.been.called.always.with(1, 2, 3);
     spy(1, 2, 3);
     spy(1, 2, 3);
-    expect(spy).to.have.been.not.called.always.with.exactly(1, 2);
-    expect(spy).to.have.been.not.called.always.with.exactly(1, 2, 3);
+    expect(spy).to.have.been.called.always.with(1, 2, 3);
+    expect(spy).to.not.have.been.called.always.with(3, 2, 1);
+    expect(spy).to.not.have.been.called.always.with(1, 2);
+    expect(spy).to.not.have.been.called.always.with(1);
+    spy(1);
+    expect(spy).to.not.have.been.called.always.with(1, 2, 3);
+    expect(spy).to.not.have.been.called.always.with(1, 2);
+    expect(spy).to.not.have.been.called.always.with(1);
   });
   });
 
 
   it('should assert that a spy has been called at least *n times', function () {
   it('should assert that a spy has been called at least *n times', function () {

+ 1 - 1
src/create-spies-group.d.ts

@@ -37,7 +37,7 @@ export declare class SpiesGroup {
   on<TObj extends object, K extends MethodKey<TObj>>(
   on<TObj extends object, K extends MethodKey<TObj>>(
     target: TObj,
     target: TObj,
     methodName: K,
     methodName: K,
-    customImpl?: TObj[K],
+    customImpl?: AnyCallable,
   ): Spy<Extract<TObj[K], AnyCallable>>;
   ): Spy<Extract<TObj[K], AnyCallable>>;
 
 
   /**
   /**

+ 4 - 4
src/create-spy.d.ts

@@ -28,7 +28,7 @@ export interface CallInfo<Args extends any[] = any[], Return = any> {
    * Ошибка, выброшенная шпионом.
    * Ошибка, выброшенная шпионом.
    * (undefined, если шпион не выбросил ошибку)
    * (undefined, если шпион не выбросил ошибку)
    */
    */
-  readonly error: unknown | undefined;
+  readonly error: any;
 }
 }
 
 
 /**
 /**
@@ -68,9 +68,9 @@ export interface Spy<TFunc extends AnyCallable = AnyCallable> {
 
 
   /**
   /**
    * Восстанавливает оригинальный метод,
    * Восстанавливает оригинальный метод,
-   * если шпион был создан для метода объекта.
-   * Ничего не делает, если шпион был создан
-   * для отдельной функции.
+   * если шпион был создан для метода объекта,
+   * и сбрасывает историю вызовов, если шпион
+   * был создан для отдельной функции.
    */
    */
   restore(): void;
   restore(): void;
 }
 }