使用 Jest 监视和模拟节点模块中 ctors 和方法的 return 值

Spying & mocking return values on ctors & methods in node module with Jest

经过很多 trial/error,在这里搜索 SO,并展示我的 Google Fu,认输并寻求帮助。

TL/DR - 尝试正确模拟节点模块,更改内部方法 return 类型,并监视节点模块内的 ctor 和方法调用。

我的具体情况是测试 Microsoft Azure 存储 blob SDK @azure/storage-blob,但问题并不特定于此包。这只是一个很好的例子,因为 4 个 LOC 的捕获实现了一项任务(将文件上传到存储容器),因为其中 2-3 个 LOC 涵盖了 4 个场景。这是我要测试的代码,并附有关于我到底想测试什么的评论:

export async function saveImage(account: string, container: string, imageBuffer: Buffer, imageName: string): Promise<void> {
  try {
    // init storage client
    // (1) want to spy on args passed into ctor
    const blobServiceClient: BlobServiceClient = new BlobServiceClient(`https://${account}.blob.core.windows.net`, new DefaultAzureCredential());
    // init container
    // (2) same as #1
    const containerClient: ContainerClient = await blobServiceClient.getContainerClient(container);
    // init block blob client
    // (3) same as #1 & #2
    const blockBlobClient: BlockBlobClient = containerClient.getBlockBlobClient(imageName);
    // save file
    // (4) same as #1,2, & 3
    // (5) manipulate returned value
    // (6) throw cause method to internally throw error
    await blockBlobClient.upload(imageBuffer, imageBuffer.length, { blobHTTPHeaders: { blobContentType: 'image/png' } });

    return Promise.resolve();
  } catch (err: Error) {
    return Promise.reject(err);
  }
}

我在 ./__mocks/@azure/storage-blob.ts 中为模块设置了一个手动模拟,如下所示:

const MockStorageBlob = jest.createMockFromModule('@azure/storage-blob');

/**
 * Utility method throw exception in the `BlockBlobClient.upload()` method.
 */
(MockStorageBlob as any).__setBlockBlobUpload_toFail = () => {
  (MockStorageBlob as any).BlobServiceClient = jest.fn().mockImplementation(() => {
    return {
      getContainerClient: jest.fn().mockReturnValue({
        getBlockBlobClient: jest.fn().mockReturnValue({
          upload: jest.fn().mockImplementation(() => {
            throw new Error('synthetic error');
          })
        })
      })
    }
  });
}

module.exports = MockStorageBlob;

在我的测试中,我可以像这样成功测试上面的#6:

import {
  BlockBlobClient,
  BlockBlobUploadResponse
} from '@azure/storage-blob';
import { saveImageToCDN as functionUnderTest } from './saveImageToCDN';

// mock Azure Storage blob NPM package
jest.mock('@azure/storage-blob');

describe('check expected with failure', () => {

  beforeEach((done) => {
    // reset number of times things have been called
    jest.clearAllMocks();
    done();
  });

  test(`it calls 'trackException()' when upload throws exception`, async (done) => {
    expect.assertions(1);

    // setup test
    require('@azure/storage-blob').__setBlockBlobUpload_toFail();

    // run SUT
    const imageBuffer = Buffer.from('test string');
    functionUnderTest(imageBuffer, 'imageName.png')
      .then(() => {
        expect(new Error('should not reach this')).toBeUndefined();
      })
      .catch((err: Error) => {
        expect(err).toBeDefined();
      })
      .finally(() => {
        done();
      });
  });

});

...但我想不出正确的语法来监视 upload() 方法(#4),或者我试图测试的任何其他东西(#1- 5).如果重要,请在 Node v14 上使用 Jest v26。

__setBlockBlobUpload_toFail 函数 return 可以引用模拟函数吗?

那会给出这样的东西:

const MockStorageBlob = jest.createMockFromModule('@azure/storage-blob');

/**
 * Utility method throw exception in the `BlockBlobClient.upload()` method.
 */
(MockStorageBlob as any).__setBlockBlobUpload_toFail = () => {
  const upload = jest.fn().mockImplementation(() => {
     throw new Error('synthetic error');
  });
  const getBlockBlobClient = jest.fn().mockReturnValue({ upload });
  const getContainerClient = jest.fn().mockReturnValue({ getBlockBlobClient });
  const BlobServiceClient = jest.fn().mockImplementation(() => {
    return {
      getContainerClient
    }
  });

  (MockStorageBlob as any).BlobServiceClient = BlobServiceClient;

  return {
    upload,
    getBlockBlobClient,
    getContainerClient,
    BlobServiceClient
  };
}

module.exports = MockStorageBlob;

在您的测试中,您将像这样检索它们:

// setup test
const mockFns = require('@azure/storage-blob').__setBlockBlobUpload_toFail();

// run SUT
const imageBuffer = Buffer.from('test string');
functionUnderTest(imageBuffer, 'imageName.png')
  .then(() => {
    expect(new Error('should not reach this')).toBeUndefined();
  })
  .catch((err: Error) => {
    expect(mockFns.getBlockBlobClient.mock.calls[0][0]).toBe('imageName.png')
    expect(err).toBeDefined();
  })
  .finally(() => {
    done();
  });