node.js - 在 Node TypeScript 中使用 jest 模拟 Stripe API
我在尝试模拟 Stripe API 以进行测试时遇到了困难。我在使用 jest 模拟函数方面经验不多,但我已经深入搜索了如何模拟 Stripe 的 API,但没有一个方法有效。
我的文件结构如下:
src/payment-gateways/stripe.ts
import Stripe from 'stripe'
import { PAYMENT_STRIPE_API_SECRET_KEY } from '../config'
const stripe = new Stripe(PAYMENT_STRIPE_API_SECRET_KEY, {
apiVersion: '2020-08-27',
})
export default stripe
然后在我的 stripe.test.ts 中,我按顺序调用我的 API 端点,以便在逻辑的中间创建一个 Stripe 客户。
我已经尝试过的是:
src/tests/__mocks__/stripe.ts
export class Stripe {}
const stripe = jest.fn(() => new Stripe())
export default stripe
src/...../stripe.test.ts
import { Stripe } from 'stripe'
Stripe.prototype.customers = {
create: jest.fn(() => ({
id: 1,
})),
} as unknown as Stripe.CustomersResource
//And also tried this without __mocks__
jest.mock('../../../../../../payment-gateways/stripe', () => {
return jest.fn(() => ({
customers: {
create: jest.fn(() =>
Promise.resolve({
id: '1',
})
),
},
}))
})
describe('stripe workflow', ()=> {
it('creates a customer', async () => {
await apollo.mutate...
.
.
.
})
})
但是我在这两种方法中都一直收到错误 [[GraphQLError: 无法读取未定义的属性 'create']]。
我想我对 Jest 与模拟对象的工作方式有些不理解
答案#1
你可以通过模拟 Stripe 的实现来完成此操作:
const mockedCustomerCreate = jest.fn();
jest.mock('stripe', () => {
const customers = jest.fn();
// @ts-ignore
customers.create = (...args) = mockedCustomerCreate(...args);
return jest.fn().mockImplementation(() => ({
customers
}));
});
describe('foo', () => {
test('bar', async () => {
mockedCustomerCreate.mockResolvedValue({});
// your test stuff
expect(mockedCustomerCreate).toBeCalledTimes(1);
});
})
关于这种方法,有几点需要注意:
// @ts-ignore是强制类型检查所必需的customers.create需要被包装在另一个函数中,否则 jest 会报错- 你需要对在 Stripe 对象上使用的每个函数做类似的操作
答案#2
在 Node 中模拟 Stripe API 真是一件麻烦事,所以我希望这个评论对某些人有用。我改进了 @MrDiggles 的答案。我正在模拟 paymentIntent.create 方法,但原理是一样的。实际上,我只是让嵌套的模拟函数隐式返回,并删除了一些不必要的行,同时满足 TS 编译器。关键部分是 create: (...args: any) => mockPaymentsIntentsCreate(...args) as unknown,,它允许在 jest 外部访问这个模拟。 mock 作用域(因此可以针对不同测试改变其行为,如示例所示),但仍然会被提升,因此在测试代码中导入 stripe 之前就会运行。我实际上并不完全确定这是如何工作的;使用 create: mockPaymentsIntentsCreate 反而会给你一个 ReferenceError: Cannot access 'mockPaymentsIntentsCreate' before initialization。希望有比我更聪明的人能回答这个问题!
index.tsimport Stripe from 'stripe';
const stripe = new Stripe('sk_test_...', {
apiVersion: '2020-08-27',
});
export const createPaymentIntent = async (parameters: Stripe.PaymentIntentCreateParams) => stripe.paymentIntents.create(parameters);
index.spec.tsimport { createPaymentIntent } from '../../src/stripe-client';
const mockPaymentsIntentsCreate = jest.fn();
jest.mock('stripe', () => jest.fn(() => ({
paymentIntents: {
create: (...args: any) => mockPaymentsIntentsCreate(...args) as unknown,
},
})));
describe('stripe-client', () => {
test('create payment intent happy path', async () => {
const paymentIntentsCreateResponse = { id: '123' };
mockPaymentsIntentsCreate.mockResolvedValueOnce(paymentIntentsCreateResponse);
const result = await createPaymentIntent({
amount: 100,
currency: 'gbp',
});
expect(result).toBe(paymentIntentsCreateResponse);
});
test('create payment intent unhappy path', async () => {
const paymentIntentsCreateResponse = 'oops';
mockPaymentsIntentsCreate.mockRejectedValueOnce(paymentIntentsCreateResponse);
await expect(createPaymentIntent({
amount: 100,
currency: 'gbp',
})).rejects.toBe(paymentIntentsCreateResponse);
});
});
