在 Jest 中模拟 @pnp/logging

Mocking @pnp/logging in Jest

我在 Spfx 项目中工作。
我有自己的 Logger class,它是 @pnp/logging/Logger.

的包装器
import * as pnpLogging from '@pnp/logging';
export class Logger {
    public static debug(message: string, notify = false, caller: string = null): void {
        pnpLogging.Logger.log( ... )
    }

此记录器随后用于各种其他 classes、组件和挂钩。

import { Logger } from "./logging";
Logger.debug('some debug message);

为其他 classes 之一创建测试时,jest 在导入 @pnp/logging.

时抛出不受支持的异常

我能够通过 office-ui-fabric-react 使用他们的 commonjs 库来解决这个问题。

"office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/",

但是,我没有看到带有@pnp/logging的这个选项。
我也尝试模拟它但没有成功。

import { nameof } from './utils';
const mockedPnpLogger = { error: (message) => { } };
jest.mock('@pnp/logging', () => ({
  ...jest.requireActual('@pnp/logging') as any,
  Logger: mockedPnpLogger
}));   
describe('utils', () => { ... }

但是仍然会抛出导出错误

SyntaxError: Unexpected token 'export'

  3 | const mockedPnpLogger = { error: (message) => { } };
  4 | jest.mock('@pnp/logging', () => ({
> 5 |     ...jest.requireActual('@pnp/logging') as any,
    |             ^
  6 |     Logger: mockedPnpLogger
  7 | }));
  8 |

我现在有点不知所措。任何人都有一些见解或可以在正确的方向上帮助我吗?

完整的错误信息

Jest encountered an unexpected token

Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

By default "node_modules" folder is ignored by transformers.

Here's what you can do:
 • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
 • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
 • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
 • If you need a custom transformation specify a "transform" option in your config.
 • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/configuration
For information about custom transformations, see:
https://jestjs.io/docs/code-transformation

Details:

C:\Development\Projects\Skan\Applications\FuhrPark\src\client\node_modules\@pnp\logging\index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export * from "./logger.js";
                                                                                  ^^^^^^

SyntaxError: Unexpected token 'export'

> 1 | import * as pnpLogging from '@pnp/logging';

Jest 配置

"jest": {
    "moduleFileExtensions": [
        "ts",
        "tsx",
        "js"
    ],
    "moduleDirectories": [
        "node_modules"
    ],
    "moduleNameMapper": {
        "\.(css|less|scss|sass)$": "identity-obj-proxy",
        "office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/",
        "^@fuhrpark/(.*)": "<rootDir>/src/webparts/fuhrpark/",
        "^@shared/(.*)": "<rootDir>/src/shared/"
    },
    "transform": {
        "^.+\.(ts|tsx)$": "ts-jest"
    },
    "transformIgnorePatterns": [
        "node_modules/(?!office-ui-fabric-react)"
    ],
    "testMatch": [
        "**/src/**/*.tests.+(ts|tsx)"
    ],
    "setupFiles": [
        "<rootDir>/src/webparts/fuhrpark/tests/tests-setup.ts"
    ],
    "testEnvironment": "node",
    "collectCoverage": true,
    "coverageReporters": [
        "json",
        "lcov",
        "text",
        "cobertura"
    ],
    "coverageDirectory": "<rootDir>/test-results",
    "reporters": [
        "default",
        "jest-junit"
    ],
    "coverageThreshold": {
        "global": {
            "branches": 0,
            "functions": 0,
            "lines": 0,
            "statements": 0
        }
    }
},
"jest-junit": {
    "output": "./test-results/summary-jest-junit.xml"
}

TypeScript 配置

{
  "extends": "./node_modules/@microsoft/rush-stack-compiler-4.2/includes/tsconfig-web.json",
  "compilerOptions": {
    "target": "es5",
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "jsx": "react",
    "declaration": true,
    "sourceMap": true,
    "experimentalDecorators": true,
    "skipLibCheck": true,
    "outDir": "lib",
    "inlineSources": false,
    "strictNullChecks": false,
    "noUnusedLocals": false,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "typeRoots": [
      "./node_modules/@types",
      "./node_modules/@microsoft"
    ],
    "types": [
      "webpack-env",
      "jest",
      "node"
    ],
    "lib": [
      "es6",
      "dom",
      "es2015.collection",
      "es2015.promise"
    ],
    "baseUrl": "src",
    "paths": {
      "@fuhrpark/*": [
        "webparts/fuhrpark/*"
      ],
      "@shared/*": [
        "shared/*"
      ]
    }
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx"
  ]
}

我将 web 对象上的 get 方法包装在一个函数中,最后我通过创建下面的模拟来正确处理它。我的包装器使用的是 .then 模式,这是模拟中双重承诺解析的原因。

重要 模拟不能在 it/test 或描述中,它必须在顶层,如 docs[ 中所述=13=]

我还在同一个 mock 中模拟了 sp.setup 函数,否则会抛出错误。因此 - 你需要根据我的发现设置一个双倍。我已经尝试了 ts-sinon/ts-mockito 等,但没有任何进展。


jest.mock('@pnp/sp', () => {
    return {
        __esModule: true,
        sp: {
            setup: jest.fn(),
            web: {
                get: () => Promise.resolve(Promise.resolve({ Title: 'Contoso Web Title' }))
            }
        }
    };
});

编辑:添加包含“select”的示例 - 这也必须是 mocked/stubbed

jest.mock('@pnp/sp', () => {
    return {
        __esModule: true,
        sp: {
            setup: jest.fn(),
            web: {
                select: () => {
                    return {
                        get: () => Promise.resolve(Promise.resolve({ Title: 'Contoso Web Title' }))
                    };
                }
            }
        }
    };
});