Как использовать 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
  },
});

Добавить cryptoglobal для вашей шутливой среды, как если бы она была в браузере. Ваш jest.config.js должен выглядеть так:

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

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

Ссылка: https://jestjs.io/docs/en/configuration

Основываясь на том, что здесь предлагали другие, я решил проблему с 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();

и все работало нормально.

      const crypto = require('crypto');
global.crypto = crypto;
Другие вопросы по тегам