如何用Jest实现Node模块构造函数、方法的Spy及返回值Mock
解决方案
1 改造手动Mock文件
修改./__mocks/@azure/storage-blob.ts,将所有Mock方法提升到模块顶层暴露,方便外部访问、修改实现和校验调用情况:
const MockStorageBlob = jest.createMockFromModule('@azure/storage-blob') as any; // 顶层定义所有Mock方法,外部可直接访问 export const mockUpload = jest.fn(); export const mockGetBlockBlobClient = jest.fn(() => ({ upload: mockUpload })); export const mockGetContainerClient = jest.fn(() => ({ getBlockBlobClient: mockGetBlockBlobClient })); export const mockBlobServiceClient = jest.fn(() => ({ getContainerClient: mockGetContainerClient })); MockStorageBlob.BlobServiceClient = mockBlobServiceClient; // 保留原有工具方法,也可直接在测试中修改mockUpload实现 MockStorageBlob.__setBlockBlobUpload_toFail = () => { mockUpload.mockImplementation(() => { throw new Error('synthetic error'); }); }; module.exports = MockStorageBlob;
2 编写测试用例覆盖所有需求
调整测试代码,直接导入暴露的Mock方法即可完成所有校验场景:
import { saveImage as functionUnderTest } from './saveImage'; // 导入Mock的方法和原始类型 import { BlobServiceClient, DefaultAzureCredential, BlockBlobUploadResponse } from '@azure/storage-blob'; // 启用手动Mock jest.mock('@azure/storage-blob'); // 类型断言,获取带Mock属性的方法引用 const mockBlobServiceClient = BlobServiceClient as jest.MockedFunction<typeof BlobServiceClient>; const mockGetContainerClient = jest.mocked((new BlobServiceClient('', new DefaultAzureCredential())).getContainerClient); const mockGetBlockBlobClient = jest.mocked(mockGetContainerClient.mock.results[0].value.getBlockBlobClient); const mockUpload = jest.mocked(mockGetBlockBlobClient.mock.results[0].value.upload); describe('saveImage 功能测试', () => { beforeEach(() => { jest.clearAllMocks(); // 每次测试前重置upload默认实现为成功返回 mockUpload.mockResolvedValue({ requestId: 'test-request-id', _response: { status: 201 } } as BlockBlobUploadResponse); }); test('正常上传场景下所有方法调用参数符合预期(覆盖需求1-4)', async () => { // 测试数据准备 const testAccount = 'test-account'; const testContainer = 'test-container'; const testBuffer = Buffer.from('test-content'); const testImageName = 'test-image.png'; // 执行待测函数 await functionUnderTest(testAccount, testContainer, testBuffer, testImageName); // (1) 校验BlobServiceClient构造参数 expect(mockBlobServiceClient).toHaveBeenCalledWith( `https://${testAccount}.blob.core.windows.net`, expect.any(DefaultAzureCredential) ); // (2) 校验getContainerClient调用参数 expect(mockGetContainerClient).toHaveBeenCalledWith(testContainer); // (3) 校验getBlockBlobClient调用参数 expect(mockGetBlockBlobClient).toHaveBeenCalledWith(testImageName); // (4) 校验upload调用参数 expect(mockUpload).toHaveBeenCalledWith( testBuffer, testBuffer.length, { blobHTTPHeaders: { blobContentType: 'image/png' } } ); }); test('自定义upload返回值场景(覆盖需求5)', async () => { // 自定义返回值 const customResponse = { requestId: 'custom-id', _response: { status: 201 } } as BlockBlobUploadResponse; mockUpload.mockResolvedValue(customResponse); const testBuffer = Buffer.from('test'); await functionUnderTest('acc', 'container', testBuffer, 'test.png'); // 校验返回值生效 expect(mockUpload.mock.results[0].value).resolves.toEqual(customResponse); }); test('upload抛出异常场景(覆盖需求6)', async () => { const testError = new Error('synthetic upload error'); mockUpload.mockRejectedValue(testError); await expect( functionUnderTest('acc', 'container', Buffer.from('test'), 'test.png') ).rejects.toThrow(testError); }); });
核心说明
原有实现无法监听调用的核心原因是:所有Mock方法都定义在__setBlockBlobUpload_toFail的闭包内部,外部无法获取到这些jest Mock实例的引用,自然无法调用toHaveBeenCalled等校验方法。改造后所有Mock方法都暴露在模块顶层,可直接访问jest Mock的所有内置API,灵活修改实现、校验调用。
内容的提问来源于stack exchange,提问作者Andrew Connell
相关产品推荐
相关产品推荐

