Reputation: 185
I'm stuck at trying to mock the Stripe API in order to perform tests. I have little experience with mocking functions with jest but I have already done a deep search on how to mock Stripe's API but non seems to be working.
My file structure is the following:
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
Then on my stripe.test.ts
I call my API endpoints in order that in the middle of the logic it creates a stripe customer.
What I have already tried was:
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...
.
.
.
})
})
But I keep getting the error [[GraphQLError: Cannot read property 'create' of undefined]]
both methods.
I guess I'm missing something on the way jest works with mocks
Upvotes: 2
Views: 4102
Reputation: 139
Mocking Stripe API in node has been a real pain so I hope this comment is useful to someone. I have improved on @MrDiggles answer. I'm mocking out paymentIntent.create
method, but the theory is the same. Really I've just made the nested mock functions return implicitly and removed a few unnecessary lines, plus satisfied TS complier. The key part here is the create: (...args: any) => mockPaymentsIntentsCreate(...args) as unknown,
which allows the mock to be accessed outside of the jest.mock
scope (and can therefore have behaviour changed for different tests, as demonstrated), but still be hoisted and so is run before stripe
is imported in the code under test. I'm actually not 100% sure how this even works; using create: mockPaymentsIntentsCreate
instead would give you a ReferenceError: Cannot access 'mockPaymentsIntentsCreate' before initialization
. Hopefully someone cleverer than me can answer that!
index.ts
import 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.ts
import { 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);
});
});
Upvotes: 2
Reputation: 768
You can accomplish this by mocking out the implementation of 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);
});
})
Couple things to note about this method:
// @ts-ignore
is necessary to force the typingcustomers.create
needs to be wrapped in another function otherwise jest will complainUpvotes: 3