簡體   English   中英

如何使用 Jest 使用 crypto 或 window.msCrypto 測試功能

[英]How to use Jest to test functions using crypto or window.msCrypto

當使用 Jest 運行單元測試時, window.crypto API 會引起問題。 我還沒有找到一種在 Jest 中合並crypto而不安裝其他軟件包的方法,這是我做不到的。 因此,在不使用另一個 npm package 的情況下,有沒有一種方法可以測試使用的函數: crypto.getRandomValues()在其中不會使 Jest 崩潰? 感謝任何鏈接、建議或提示

這應該做。 使用以下代碼全局設置crypto屬性。 這將允許 Jest 訪問window.crypto並且不會導致任何問題。

const crypto = require('crypto');

Object.defineProperty(global.self, 'crypto', {
  value: {
    getRandomValues: arr => crypto.randomBytes(arr.length)
  }
});

像@RwwL 一樣,接受的答案對我不起作用。 我發現這個庫中使用的 polyfill 確實有效: commit with polyfill

//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,
  },
});

對於那些使用Jest >=28jsdom ( jest-environment-jsdom ) 環境的人,你應該將替換模塊定義為getter

//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但其他實現也應該有效。

為您的 jest 環境添加crypto全局,就像在瀏覽器中一樣。 您的 jest.config.js 應如下所示:

const {defaults} = require('jest-config');

module.exports = {
  globals: {
    ...defaults.globals,
    crypto: require('crypto')
  }
};

參考: https : //jestjs.io/docs/en/configuration#globals-object

源自 AIVeligs 答案:

由於我在 Jest 中使用了“節點”環境,因此我不得不使用

module.exports = {
  preset: "ts-jest",
  testEnvironment: "node",
  globals: {
    crypto: {
      getRandomValues: (arr) => require("crypto").randomBytes(arr.length),
    },
  },
};

我正在使用 vue-jest,對我jest.config.jsjest.config.js文件中的以下配置:

module.exports = {
   ...
   setupFiles: [
      '<rootDir>/tests/settings/jest.crypto-setup.js',
   ],
};

jest.crypto-setup.js

global.crypto = { 
     getRandomValues: (arr) => require('crypto').randomBytes(arr.length) 
};

直接在module.exports添加getRandomValues函數定義不起作用,因為globals對象必須是 json-serializable(如此處指定: https : module.exports )。

當前答案中的 polyfill 不完整,因為Crypto.getRandomValues()就地修改了它的參數並返回它。 您可以通過運行類似const foo = new Int8Array(8); console.log(foo === crypto.getRandomValues(foo))來驗證這一點。 const foo = new Int8Array(8); console.log(foo === crypto.getRandomValues(foo))在您的瀏覽器控制台中,它將打印true

getRandomValues()也不接受Array作為其參數,它只接受整數TypedArray s Node.js 的crypto.randomBytes()函數不適合這個 polyfill,因為它輸出原始字節,而getRandomValues()可以接受元素高達 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 },
})

對於 nodeJS + 打字稿,只需使用global而不是global.self

import crypto from 'crypto'

Object.defineProperty(global, 'crypto', {
  value: {
    getRandomValues: (arr:any) => crypto.randomBytes(arr.length)
  }
});

我在 Angular 8 中遇到了這個問題,對使用 uuid 生成器的 lib 進行了 Jest 測試。 在開玩笑的測試設置中,我嘲笑這個:

Object.defineProperty(global.self, 'crypto', {
  value: {
    getRandomValues: arr => arr
  },
});
const crypto = require('crypto');
global.crypto = crypto;

在使用 Jest 進行測試期間,默認的crypto依賴項對我不起作用。

相反,我使用了@peculiar/webcrypto庫:

yarn add -D @peculiar/webcrypto

然后在您的 Jest 設置文件中,添加以下內容:

import { Crypto } from "@peculiar/webcrypto";


window.crypto = new 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”等。

dspacejs 的答案幾乎對我有用,除了我和Mozgor有同樣的問題。 我收到一條錯誤消息,說 window.crypto 是只讀的。 您可以使用 Object.assign 而不是直接嘗試覆蓋它。

使用yarn add -D @peculiar/webcryptonpm i --save-dev @peculiar/webcrypto

然后將以下內容添加到您的 Jest 設置文件中:

import { Crypto } from "@peculiar/webcrypto";

Object.assign(window, {
  crypto: new Crypto(),
})

在默認配置中, Jest 假設您正在測試 Node.js 環境 但是當您使用window對象的方法遇到錯誤時,您可能正在制作一個 Web 應用程序。

所以如果你正在制作一個網絡應用程序,你應該使用“jsdom”作為你的“testEnvironment”。 為此,請將"testEnvironment": "jsdom",插入到您的 Jest 配置中。

如果您維護一個“jest.config.js”文件,則將其添加如下:

module.exports = {
   ...
   "testEnvironment": "jsdom",
   ...
};

或者,如果像我一樣,您將 Jest 配置保存在“package.json”中:

{
    ...,
    "jest": {
        ...,
        "testEnvironment": "jsdom",
        ...
    },
    ...
}

依賴注入是解決這個問題的一種方法。

Node.js 提供標准 Web Crypto API 的實現。使用 require('node:crypto').webcrypto 訪問此模塊。

因此,您將密碼 object 傳遞給依賴它的代碼。

請注意我們在調用方法 utils.aesGcmEncrypt 時如何“注入”正確的密碼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);
});

晚會遲到了,但我通常會這樣做:

// 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' });
  });
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM