Как шутка позволяет изменять модули?

В этом вопросе, который я задал здесь:

Почему изменение модуль обновляет ссылку при вызове этого модуля из другого модуля, но не при вызове из самого себя?

Я спрашиваю о природе мутации модуля.

Однако, как оказалось, модули ES6 фактически не могут быть изменены - все их свойства рассматриваются как константы. (См. этот ответ)

Но каким-то образом - когда Jest тестирует модули - они могут быть видоизменены, и именно так Jest позволяет имитировать.

Как это происходит?

Я полагаю, что это плагин babel, который работает - перенос модуля в модули CommonJS? Есть ли по этому поводу документация?

Есть ли способ просмотреть транспилированный код?


person dwjohnston    schedule 25.01.2019    source источник


Ответы (1)


Модули ES6 фактически не могут быть изменены - все их свойства рассматриваются как константы.

Интересный. Вы правы, даже в таком простом случае:

import * as lib from "./lib";  // import an ES6 module
const spy = jest.spyOn(lib, 'someFunc');  // spy on someFunc

... технически недопустимо, поскольку jest.spyOn заменяет метод объекта на шпион, и lib.someFunc должен быть привязкой к someFunc в модуле ES6.


Но каким-то образом - когда Jest тестирует модули - они могут быть видоизменены, и именно так Jest позволяет имитировать.

Как это происходит?

Они могут быть изменены только потому, что Jest на самом деле не использует модули ES6.

(Я полагаю, для полноты картины возможно можно запустить Jest с использованием реальных модулей ES6, используя Node's экспериментальная поддержка модулей ES6, но я не пробовал).


Я полагаю, что это плагин babel, который работает - транспиляция модуля ... Есть ли документация по этому поводу?

babel-jest автоматически устанавливается при установке Jest и автоматически преобразует файлы, если в вашем проект. Чтобы избежать этого, можно явно сбросить transform параметр конфигурации.

Таким образом, по умолчанию Jest будет использовать babel-jest, который переносит исходный код с помощью babel (и выполняет еще несколько вещей, например подъем вызовов к jest.mock).

Обратите внимание, что Jest также можно настроить с помощью transform, который отображает обычные выражения к путям к трансформерам.


Есть ли способ просмотреть транспилированный код?

да. Преобразования выполняются в jest-runtime --showConfig, который выведет config, используемый при запуске Jest. Расположение кеша можно найти, посмотрев значение cacheDirectory.

Затем запустите Jest с параметром --clearCache, чтобы очистить кеш.

Наконец, запустите Jest в обычном режиме, и каталог кеша будет содержать перенесенный код для вашего проекта.


Пример

Последняя версия Jest (v24) перенесет этот код:

// lib.js
export const someFunc = () => 1;


// code.js
import { someFunc } from './lib';
export const func = () => someFunc() + 1;


// code.test.js
import { func } from './code';
import * as lib from './lib';

test('func', () => {
  const spy = jest.spyOn(lib, 'someFunc');
  func();
  expect(spy).toHaveBeenCalled();  // SUCCESS
});

...к этому:

// lib.js
"use strict";
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.someFunc = void 0;
const someFunc = () => 1;
exports.someFunc = someFunc;


// code.js
"use strict";
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.func = void 0;
var _lib = require("./lib");
const func = () => (0, _lib.someFunc)() + 1;
exports.func = func;


// code.test.js
"use strict";
var _code = require("./code");
var lib = _interopRequireWildcard(require("./lib"));
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
test('func', () => {
  const spy = jest.spyOn(lib, 'someFunc');
  (0, _code.func)();
  expect(spy).toHaveBeenCalled(); // SUCCESS
});

Строка import * as lib from 'lib'; обрабатывается _interopRequireWildcard, который использует require под капотом.

Каждый вызов require будет возвращать один и тот же объект, если он разрешит одно и то же файл, поэтому code.js и code.test.js получают один и тот же объект из require('./lib').

someFunc экспортируется как exports.someFunc, что позволяет переназначить его.


Так что да, вы совершенно правы. Подобное слежение (или насмешка) работает только потому, что модули ES6 babel передаются в Node модули таким образом, чтобы они могли видоизменяться.

person Brian Adams    schedule 28.01.2019