在 TypeScript 中使用 proxyquire 和 sinon 存根 adm-zip
Stub adm-zip with proxyquire and sinon in TypeScript
我想开玩笑地对 class zip.adapter.ts 进行单元测试。我尝试了很多不同的方法来 mock/stub adm-zip 包,但没有任何效果。
我第一次尝试 ts-mock-imports but it always fails if I try to mock adm-zip. Then I tried 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);
});
});
Top-level import 仅导入 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
以允许模拟模块为 re-imported.
我想开玩笑地对 class zip.adapter.ts 进行单元测试。我尝试了很多不同的方法来 mock/stub adm-zip 包,但没有任何效果。
我第一次尝试 ts-mock-imports but it always fails if I try to mock adm-zip. Then I tried 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);
});
});
Top-level import 仅导入 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
以允许模拟模块为 re-imported.