Как использовать Jest для тестирования функций с использованием crypto или window.msCrypto
При запуске модульных тестов с помощью Jest в реагировании API window.crypto вызывает проблемы. Я не нашел способа внедрить crypto в Jest без установки других пакетов, чего я не могу сделать. Таким образом, без использования другого пакета npm есть способ протестировать функции, которые используют: crypto.getRandomValues()
в них не разбивается Джест? Любые ссылки, советы или советы приветствуются
18 ответов
Это должно сделать это. Используйте следующий код для настройки crypto
собственность во всем мире. Это позволит Jest получить доступ window.crypto
и не вызовет никаких проблем.
const crypto = require('crypto');
Object.defineProperty(global.self, 'crypto', {
value: {
getRandomValues: arr => crypto.randomBytes(arr.length),
},
});
Как и @RwwL, принятый ответ у меня не сработал. Я обнаружил, что полифилл, используемый в этой библиотеке, действительно работал: фиксация с полифилом
//setupTests.tsx
const nodeCrypto = require('crypto');
window.crypto = {
getRandomValues: function (buffer) {
return nodeCrypto.randomFillSync(buffer);
}
};
//jest.config.js
module.exports = {
//...
setupFilesAfterEnv: ["<rootDir>/src/setupTests.tsx"],
};
Начиная с узла 15.x вы можете использовать
crypto.webcrypto
например.
import crypto from "crypto";
Object.defineProperty(global.self, "crypto", {
value: {
subtle: crypto.webcrypto.subtle,
},
});
Для nodeJS + typescript просто используйте global
вместо того global.self
import crypto from 'crypto'
Object.defineProperty(global, 'crypto', {
value: {
getRandomValues: (arr:any) => crypto.randomBytes(arr.length)
}
});
Для тех, кто используетjsdom
(jest-environment-jsdom
) среда сJest >=28
вы должны определить модуль замены как геттер .
//jest.config.js
module.exports = {
testEnvironment: "jsdom",
rootDir: './',
moduleFileExtensions: ['ts', 'js'],
setupFilesAfterEnv: ["<rootDir>/test/setup-env.tsx"],
preset: 'ts-jest',
};
// setup-env.tsx
const { Crypto } = require("@peculiar/webcrypto");
const cryptoModule = new Crypto();
Object.defineProperty(window, 'crypto', {
get(){
return cryptoModule
}
})
Я использую@peculiar/webcrypto
но другие реализации также должны работать.
Полифиллы в текущих ответах неполные, так как
Crypto.getRandomValues()
изменяет свой аргумент на месте, а также возвращает его. Вы можете проверить это, запустив что-то вроде
const foo = new Int8Array(8); console.log(foo === crypto.getRandomValues(foo))
в консоли вашего браузера, которая напечатает
true
.
также не принимает
Array
в качестве аргумента принимает только целое число s . Node.js'
crypto.randomBytes()
не подходит для этого полифилла, так как выводит необработанные байты , тогда как может принимать массивы целых чисел со знаком с элементами до 32 бит. Если вы пытаетесь
crypto.getRandomValues(new Int32Array(8))
в вашем браузере вы можете увидеть что-то вроде
[ 304988465, -2059294531, 229644318, 2114525000, -1735257198, -1757724709, -52939542, 486981698 ]
. Но если вы попробуете
node -e 'console.log([...require("crypto").randomBytes(8)])'
в командной строке вы можете увидеть
[ 155, 124, 189, 86, 25, 44, 167, 159 ]
. Очевидно, что они не эквивалентны, и ваш тестируемый компонент может вести себя не так, как ожидалось, если тестируется с последним.
Последние версии Node.js решают эту проблему с помощьюwebcrypto
модуль (должен быть вопрос настройки
globalThis.crypto = require('crypto').webcrypto
). Если вы используете более старую версию Node (v14 или ниже), возможно, вам повезет больше, используяcrypto.randomFillSync()
, который можно использовать в качестве замены для
getRandomValues()
поскольку он изменяет переданный буфер/
TypedArray
на месте.
В вашем установочном файле Jest (нельзя установить черезglobals
конфигурации , поскольку он допускает только значения, совместимые с JSON):
const { randomFillSync } = require('crypto')
Object.defineProperty(globalThis, 'crypto', {
value: { getRandomValues: randomFillSync },
})
Я использую vue-jest, и у меня сработала следующая конфигурация в
jest.config.js
файл:
const crypto = require('crypto');
module.exports = {
globals: {
crypto: {
getRandomValues: (arr) => crypto.randomBytes(arr.length),
},
},
preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
transform: {
'^.+\\.vue$': 'vue-jest',
}
};
Исходя из ответа AIVeligs:
Поскольку в Jest я использую среду "node", мне пришлось использовать
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
globals: {
crypto: {
getRandomValues: (arr) => require("crypto").randomBytes(arr.length),
},
},
};
По умолчанию
crypto
зависимость не работала у меня во время тестирования с Jest.
Вместо этого я использовал
@peculiar/webcrypto
библиотека:
yarn add -D @peculiar/webcrypto
Затем в вашем установочном файле Jest просто добавьте это:
import { Crypto } from "@peculiar/webcrypto";
window.crypto = new Crypto();
Ответ dspacejs почти сработал для меня, за исключением того, что у меня была та же проблема, что и у Мозгора. Я получил сообщение об ошибке, говорящее, что window.crypto доступен только для чтения. Вы можете использовать Object.assign вместо прямой попытки перезаписать его.
Установите @peculiar/webcrypto с
yarn add -D @peculiar/webcrypto
или же
npm i --save-dev @peculiar/webcrypto
Затем добавьте в установочный файл Jest следующее:
import { Crypto } from "@peculiar/webcrypto";
Object.assign(window, {
crypto: new Crypto(),
})
У меня есть эта проблема в Angular 8 с тестами Jest для lib, которые используют генератор uuid. В тестовой настройке я издеваюсь над этим:
Object.defineProperty(global.self, 'crypto', {
value: {
getRandomValues: arr => arr
},
});
Добавить crypto
global для вашей шутливой среды, как если бы она была в браузере. Ваш jest.config.js должен выглядеть так:
const {defaults} = require('jest-config');
module.exports = {
globals: {
...defaults.globals,
crypto: require('crypto')
}
};
Основываясь на том, что здесь предлагали другие, я решил проблему с window.crypto.subtle.digest следующим образом:
Object.defineProperty(global.self, "crypto", {
value: {
getRandomValues: (arr: any) => crypto.randomBytes(arr.length),
subtle: {
digest: (algorithm: string, data: Uint8Array) => {
return new Promise((resolve, reject) =>
resolve(
createHash(algorithm.toLowerCase().replace("-", ""))
.update(data)
.digest()
)
);
},
},
},
});
Или, если вы не используете Typescript:
Object.defineProperty(global.self, "crypto", {
value: {
getRandomValues: (arr) => crypto.randomBytes(arr.length),
subtle: {
digest: (algorithm, data) => {
return new Promise((resolve, reject) =>
resolve(
createHash(algorithm.toLowerCase().replace("-", ""))
.update(data)
.digest()
)
);
},
},
},
});
Переформатирование строки необязательно. Также возможно жестко закодировать алгоритм, например, указав «sha256» или «sha512» или что-то подобное.
опаздываю на вечеринку, но обычно делаю что-то вроде:
// module imports here
// important: the following mock should be placed in the global scope
jest.mock('crypto', function () {
return {
randomBytes: jest
.fn()
.mockImplementation(
() =>
'bla bla bla'
),
}
});
describe('My API endpoint', () => {
it('should work', async () => {
const spy = jest.spyOn(DB.prototype, 'method_name_here');
// prepare app and call endpoint here
expect(spy).toBeCalledWith({ password: 'bla bla bla' });
});
});
Внедрение зависимостей — один из способов решить эту проблему.
Node.js предоставляет реализацию стандартного Web Crypto API. Используйте require('node:crypto').webcrypto для доступа к этому модулю.
Таким образом, вы передаете криптообъект коду, который от него зависит.
Обратите внимание, как мы «вводим» правильный криптообъект при вызове метода.utils.aesGcmEncrypt
test("Encrypt and decrypt text using password", async () => {
const password = "Elvis is alive";
const secret =
"surprise action festival assume omit copper title fit tower will chalk bird";
const crypto = require("crypto").webcrypto;
const encrypted = await utils.aesGcmEncrypt(crypto, secret, password);
const decrypted = await utils.aesGcmDecrypt(crypto, encrypted, password);
expect(decrypted).toBe(secret);
});
В конфигурации по умолчанию Jest предполагает, что вы тестируете среду Node.js. Но когда вы получаете ошибки, используя методы
window
объект, вы, вероятно, делаете веб-приложение.
Поэтому, если вы создаете веб-приложение, вы должны использовать «jsdom» в качестве «testEnvironment». Для этого вставьте
"testEnvironment": "jsdom",
в ваши конфигурации Jest.
Если вы поддерживаете файл jest.config.js, добавьте его следующим образом:
module.exports = {
...
"testEnvironment": "jsdom",
...
};
Или, если, как и я, вы храните конфиги Jest в «package.json»:
{
...,
"jest": {
...,
"testEnvironment": "jsdom",
...
},
...
}
Я реализовал его с помощью jest, и после обновления версии jest его не удалось выполнить. Раньше я использовал таким образом:
global.crypto = {
getRandomValues: jest.fn();
}
После обновления произошел сбой. Итак, я попробовал следующим образом:
global.crypto.getRandomValues = jest.fn();
и все работало нормально.