mkdir blog && cd $_

ITエンジニアの雑感

TypeError: Cannot read properties of undefined (reading 'getRandomValues')

TypeError: Cannot read properties of undefined (reading 'getRandomValues') に対応したメモ。

前提

乱数文字列を生成する関数

function getRandom() {
  const S = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  const N = 64;
  const str = Array.from(window.crypto.getRandomValues(new Uint8Array(N)))
    .map((n) => S[n % S.length])
    .join('');
  return str;
}

...

function foo() {
   const rand = getRandom();
...
}

fooのテストコード

describe('foo', () => {
  test('success', async () => {
    const result = foo();
    expect(result).toEqual(...);
  });
});

テストを実行すると以下のようになる。

yarn test
yarn run v1.22.22
 FAIL  xxx.test.ts
  foo
    ✕ success

  ● foo › success

    TypeError: Cannot read properties of undefined (reading 'getRandomValues')

      12 |   const S = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
      13 |   const N = 64;
    > 14 |   const str = Array.from(window.crypto.getRandomValues(new Uint8Array(N)))
         |                                        ^
      15 |     .map((n) => S[n % S.length])
      16 |     .join('');
      17 |   return str;

ステップ1

当該テストの中で、getRandomValuesをmockにする。

fooのテストコード

describe('foo', () => {
  test('success', async () => {
    const mGetRandomValues = jest
      .fn()
      .mockReturnValueOnce([
        23, 52, 227, 35, 236, 241, 244, 212, 231, 189, 171, 160, 70, 146, 227,
        90, 99, 2, 161, 45, 164, 115, 254, 138, 146, 243, 55, 107, 31, 87, 0,
        235, 96, 216, 125, 200, 88, 119, 205, 209, 224, 185, 235, 101, 168, 202,
        134, 70, 38, 175, 41, 5, 1, 102, 60, 2, 46, 216, 212, 17, 176, 231, 233,
        167,
      ]);
    Object.defineProperty(window, 'crypto', {
      value: { getRandomValues: mGetRandomValues },
    });

    const result = foo();
    expect(result).toEqual(...);
  });
});

これでも動作はするが、 - テストケース毎にmockを記述するのは煩雑になる - fooのテストケースで、内部で呼び出している関数のmock追加することに違和感を感じる。認知負荷が高い気がする。

ステップ2

  • fooのテストはそのままにしたい。
  • テストケース毎にmockををするのではなく、設定を一箇所にしたい

前提

jest.config.js

/** @type {import('jest').Config} */
const config = {
...
  "setupFiles": [
    "<rootDir>/src/jest/jest.polyfills",
  ],
...

改善

src/jest/jest.polyfills.js

...
const crypto = require('crypto');
Object.defineProperty(globalThis, 'crypto', {
  value: {
    getRandomValues: arr => crypto.randomBytes(arr.length)
  }
});
...

セットアップで、mockの設定をすることで個別のテストケースではmockしないようにする。

参考

stackoverflow.com