【问题标题】:Stub adm-zip with proxyquire and sinon in TypeScript在 TypeScript 中使用 proxyquire 和 sinon 存根 adm-zip
【发布时间】:2021-02-16 22:21:54
【问题描述】:

我想用玩笑对类 zip.adapter.ts 进行单元测试。我尝试了很多不同的方法来模拟/存根 adm-zip 包,但没有任何效果。

我首先尝试了ts-mock-imports,但如果我尝试模拟 adm-zip,它总是会失败。然后我尝试了sinon,但它要么无法存根 adm-zip,要么只是没有存根。 我最后的手段是将 sinon 与 proxyquire 结合起来,但这似乎也不起作用....

有人知道为什么这不起作用吗?当测试调用 unzip 方法时,其中的代码仍然使用真正的 adm-zip 实现...

(我知道单元测试没有多大意义,因为一切都是模拟的,但我必须这样做,因为我无法更改测试覆盖率规则)

zip.adapter.ts

import * as admZip from 'adm-zip';

export class ZipAdapter {
    constructor() {}

    unzip(zip: Buffer, path: string) {
        const unzip = new admZip(zip);
        unzip.extractAllTo(path, true);
    }
}

zip.adapter.spec.ts

import * as sinon from 'sinon';
import { ZipAdapter } from './zip.adapter';
import * as proxyquire from 'proxyquire';

describe('Zip Adapter', () => {
    let zipAdapter: ZipAdapter;

    beforeEach(() => {
        const admZipInstance = { extractAllTo: sinon.stub() };
        const admZipStub = sinon.stub().callsFake(() => admZipInstance);
        const moduleStub = proxyquire('./zip.adapter.ts', { 'adm-zip': admZipStub });

        zipAdapter = new moduleStub.ZipAdapter();
    });

    it('should be defined', () => {
        expect(zipAdapter).toBeDefined();
    });

    it('should have called extractAllTo', () => {
        zipAdapter.unzip(Buffer.from(''), 'test');
    });
});

更新:

我使用 Jest 进行了测试,但前提是我需要()我的模块。如果我在没有 require() 的情况下使用我的导入,则模拟不起作用。是否可以摆脱 require() 而只使用导入?

import { ZipAdapter } from './zip.adapter';

describe('Zip Adapter', () => {
    let zipAdapter: ZipAdapter;
    let admZipExtractAllMock: jest.Mock<any, any>;

    beforeEach(() => {
        const admZipMock = jest.fn();
        admZipExtractAllMock = jest.fn();
        admZipMock.mockImplementation(() => {
            return { extractAllTo: admZipExtractAllMock };
        });
        jest.mock('adm-zip', () => admZipMock);

        const zipAdapterModule = require('./zip.adapter');
        zipAdapter = new zipAdapterModule.ZipAdapter();
    });

    it('should be defined', () => {
        expect(zipAdapter).toBeDefined();
    });

    it('should have called extractAllTo', () => {
        zipAdapter.unzip('unit', 'test');
        expect(admZipExtractAllMock.mock.calls.length).toBe(1);
    });
});

【问题讨论】:

  • 为什么在 Jest 中使用 Sinon 和 Proxyquire?它应该取代两者。
  • 我没有真正的理由不使用 Jest 进行模拟。我只是有更多使用诗乃的经验,直到现在我从来没有遇到过问题。我将测试更改为使用 Jest,并且可以使用它,但我真的不明白为什么我必须像这样使用 require()。
  • Jest 功能和模块模拟的集成足够紧密,因此更容易使用自己的功能。我希望 Proxyquire 与 Jest 不兼容,因为两者都以类似的方式与 Node 模块 API 混淆。生产代码中还有一个问题,import * as admZip from 表示 TS 模块互操作配置不正确,因为 * import 不能成为规范的函数。 CommonJS 模块应该是默认导入,import admZip from 这意味着如果没有修复,答案中建议的模拟可能对您不起作用,请参阅staging-typescript.org/tsconfig#esModuleInterop

标签: typescript jestjs sinon proxyquire adm-zip


【解决方案1】:

顶级导入仅导入ZipAdapter 类型,因此在第一次使用require 导入时对其进行评估。如果在导入后用jest.mock 模拟,这不会影响导入的模块。

如果需要对所有测试进行模拟,则应在顶层模拟并导入:

import * as zipAdapterModule from './zip.adapter';

jest.mock('adm-zip', () => {
  let admZipExtractAllMock = jest.fn();
  return {
    __esModule: true,
    admZipExtractAllMock,
    default: jest.fn(() => ({ extractAllTo: admZipExtractAllMock }))
});

jest.mock 在顶层被提升到import 上方。 admZipExtractAllMock spy 暴露为命名导出,以便能够随时更改实现,最好使用 Once 方法,以免影响其他测试。

如果不需要对某些测试进行模拟,或者 Jest spy API 不足以更改实现,则需要使用 jest.mock 模拟并在测试中使用 require 导入,如 OP 中所示。在这种情况下,应添加 jest.resetModules 以允许重新导入模拟模块。

【讨论】:

    猜你喜欢
    • 2018-08-12
    • 2021-01-21
    • 2015-07-08
    • 1970-01-01
    • 2015-07-10
    • 2017-08-02
    • 1970-01-01
    • 2020-10-24
    • 2018-05-05
    相关资源
    最近更新 更多