TypeScript测试云函数:如何Stub外部依赖类方法并捕获调用参数?
测试TypeScript编写的Google Cloud Function:验证publishMessage参数
问题
我想要测试一个用TypeScript编写的Google Cloud Function,该函数调用了Topic类的成员方法publishMessage,需要让单元测试捕获传递给publishMessage的参数并验证。
我试过用sinon或proxyquire实现,但相关示例都是JavaScript的require()语法,而且Stub的是模块级静态函数,不知道怎么适配TypeScript的import语法,也不清楚如何Stub类内部的方法。
要测试的代码大致如下:
import {PubSub} from "@google-cloud/pubsub" class Foo { private pubSub = new PubSub() private topic = this.pubSub.topic("foo-topic") public publishMessage() { const data = Buffer.from("content") return this.topic.publishMessage({data}) } } const foo = new Foo() // 测试时会调用这个导出的函数 export const fooPublishMessage = foo.publishMessage
当前用mocha和chai测试,也可以用其他工具。
解决方案
第一步:改造代码,支持依赖注入
原代码中直接在类内部实例化PubSub,属于硬编码依赖,无法直接替换为测试用的Stub对象。首先要调整Foo类的结构,通过构造函数注入Topic实例,让测试可以灵活替换依赖:
import {PubSub, Topic} from "@google-cloud/pubsub" class Foo { private topic: Topic // 构造函数可选注入Topic实例,生产环境用默认逻辑初始化 constructor(topic?: Topic) { if (topic) { this.topic = topic } else { const pubSub = new PubSub() this.topic = pubSub.topic("foo-topic") } } public publishMessage() { const data = Buffer.from("content") return this.topic.publishMessage({data}) } } // 生产环境使用默认构造 const foo = new Foo() export const fooPublishMessage = foo.publishMessage
方法一:用Sinon Stub Topic方法
结合Sinon和chai,创建Stub版的Topic对象,验证publishMessage的调用参数:
- 安装依赖:
npm install sinon @types/sinon --save-dev
- 测试代码:
import { expect } from 'chai' import sinon from 'sinon' import { Topic } from '@google-cloud/pubsub' import { Foo } from '../src/foo' describe('Foo.publishMessage', () => { it('调用publishMessage时传入正确的data参数', async () => { // 创建publishMessage方法的Stub,模拟返回值 const publishStub = sinon.stub().resolves('fake-message-id') // 构造符合Topic类型的Stub对象 const mockTopic = { publishMessage: publishStub } as unknown as Topic // 传入Stub的Topic实例创建Foo const foo = new Foo(mockTopic) await foo.publishMessage() // 验证调用次数和参数 expect(publishStub.calledOnce).to.be.true const [callParam] = publishStub.firstCall.args expect(callParam.data.toString()).to.equal('content') }) })
方法二:用ts-mockito实现类型友好的Mock
如果希望测试代码更贴合TypeScript的类型系统,推荐使用ts-mockito,它能自动推断类型,无需手动断言:
- 安装依赖:
npm install ts-mockito @types/ts-mockito --save-dev
- 测试代码:
import { expect } from 'chai' import { mock, instance, verify, when } from 'ts-mockito' import { Topic } from '@google-cloud/pubsub' import { Foo } from '../src/foo' describe('Foo.publishMessage', () => { it('向publishMessage传递正确的data参数', async () => { // 创建Topic的Mock实例 const mockTopic = mock(Topic) // 模拟publishMessage的返回逻辑 when(mockTopic.publishMessage({ data: Buffer.from('content') })).thenResolve('fake-id') // 传入Mock实例创建Foo const foo = new Foo(instance(mockTopic)) await foo.publishMessage() // 验证publishMessage被正确调用 verify(mockTopic.publishMessage({ data: Buffer.from('content') })).once() }) })
为什么不直接Stub PubSub模块?
直接Stub模块级的PubSub构造函数需要用到proxyquire或rewire这类工具,但它们对TypeScript的支持不够友好,测试代码会更复杂。依赖注入的方式不仅让测试更清晰,还符合SOLID原则,提升了代码的可维护性。
内容的提问来源于stack exchange,提问作者Christopher Simmons
相关产品推荐
相关产品推荐

