You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 09:54:01