Пример использования метапрограммирования
Для чего нужны шпионы
Шпионы позволяют проверять функции для определенных шаблонов вызовов, например, для данного экземпляра класса:
const foo = new (class Foo { bar () { } })
Мы можем подключить шпиона к методу bar:
foo.bar = chai.spy (foo.bar)
Затем сгенерируйте несколько вызовов:
foo.bar ('qux')
И, наконец, убедитесь, что он был правильно вызван:
foo.bar.should.have.been.called.with ('qux').once
Пока все выглядит хорошо. Но когда у вас есть много тестов с большим количеством классов и методов, это явное переопределение метода как бы мешает ...
Собирается декларативно
Было бы однозначно круто, если бы это можно было записать так:
const foo = new (class Foo { @will.have.been.called.with (42).once bar () { } @will.have.been.called.twice zap () { } }) foo.bar (42) foo.zap (); foo.zap ()
В этой статье я покажу вам, как реализовать это с помощью новейших функций метапрограммирования ECMAScript: декораторов, прокси и символов.
Реализация
Мы нацелены на Mocha, подключившись к его функции `it`, собирая вызовы декораторов и выполняя шпионские проверки после завершения теста.
Вот как выглядит обычный тест:
import 'chai-spies-decorators' describe ('chai-spies-decorator', () => { it ('works', () => { /* Here's our test */ }) })
Основная идея состоит в том, чтобы изменить функцию `it` так, чтобы она заключала в себе наш тест следующим образом:
checks = gatherChecks () /* Here's our test */ checks.forEach (check => check ())
Вся магия скрыта внутри gatherChecks. Он определяет глобальное свойство will:
Object.defineProperty (global, 'will', { configurable: true, get () { ... }
Что при доступе сгенерирует прокси-объект, который фиксирует цепочку обращений к свойствам и вызовов функций:
will.have.been.called.with (42).once // captures ['have', 'been', 'called', 'with', [42], 'once']
Когда эта цепочка применяется к методу класса в качестве декоратора…
@will.have.been.called.with (42).once method () { ... }
… Он выводит средство доступа к свойству, в котором мы генерируем (и запоминаем) «шпионскую» версию этого метода, а также обратный вызов проверки, который гарантирует, что метод был вызван в соответствии с контрактом:
const memo = Symbol () // for referencing the hidden state property return { get () { if (this[memo]) { return this[memo] } else { const spied = chai.spy (method) checks.push (() => { // executes the captured ['have', 'been', ...] // on a spied method }) return (this[memo] = spied) } } }
Проверка выполняется после завершения теста, генерируя цепочку утверждений Chai из захваченного представления массива. Таким образом, мы можем переводить вызовы API, даже не зная о них заранее.
Полный код
Реализация доступна на GitHub:
Как пакет NPM:
npm install chai-spies-decorators