nestjs-stripe
nestjs-stripe copied to clipboard
How to Mock Stripe Client for Unit Tests
I'm trying to unit-test a service which is using the Stripe class. The setup is similarly as described in the README
@Injectable()
export class MyService {
constructor(@InjectStripe() private readonly stripe: Stripe) {}
createCheckout() {
this.stripe.checkout.sessions.create({/** **/});
}
}
In the unit test I provide a mock as known from other services.
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MyService, { provide: Stripe, useClass: StripeMock }],
}).compile();
});
The mock looks like this:
class Sessions {
create(): void {
return null;
}
}
class Checkout {
sessions: Sessions;
constructor() {
this.sessions = new Sessions();
}
}
export class StripeMock {
checkout: Checkout;
constructor() {
this.checkout = new Checkout();
}
}
The problem is that NestJs dose not recognize the mock completely. It throws the given error:
Nest can't resolve dependencies of the CheckoutService (?). Please make sure that the argument StripeToken at index [0] is available in the RootTestModule context.
Potential solutions:
- If StripeToken is a provider, is it part of the current RootTestModule?
- If StripeToken is exported from a separate @Module, is that module imported within RootTestModule?
Are there some recommendations how to mock the Stripe instance in tests? It could be useful to add a short section to the documentation about testing/mocking, because it seems not straight forward as known from other NestJs services. Maybe it make sense to provide sth. like a StripeTestingModule within this package? If someone explains the problem to me I can get my hands dirty adding a related "Unit Testing/Mock" section to the docs.
I also came across the same issue and started digging into the source code to understand what StripeToken dependency was all about. It turns out it's an exported constant.
After some testing I came up with two possible solutions in order to generate a proper Stripe mock
- Mock the
StripeTokendependency as an import in your test module (and export it)
const stripeMock = () => ({
refunds: { create: jest.fn() },
invoices: { list: jest.fn() },
});
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
{
module: class FakeModule {},
providers: [{ provide: 'StripeToken', useValue: {} }],
exports: ['StripeToken'],
},
],
providers: [
MyService,
{ provide: Stripe, useFactory: stripeMock }
],
}).compile();
let stripe = module.get(Stripe);
});
- Mock
StripeTokeninstead ofStripe(less verbose, same as above)
const stripeMock = () => ({
refunds: { create: jest.fn() },
invoices: { list: jest.fn() },
});
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
MyService,
{ provide: 'StripeToken', useFactory: stripeMock },
]
}).compile();
let stripe = module.get('StripeToken');
});
Here's the way I got it working:
- Create a mock of the node
stripelibrary
// __mocks__/stripe.mock.ts
import Stripe from 'stripe';
import * as faker from 'faker';
export const mockStripe = () => {
return {
customers: {
retrieve: jest.fn((customerId: string) =>
Promise.resolve({
id: customerId,
} as Stripe.Response<Stripe.Customer | Stripe.DeletedCustomer>),
),
update: jest.fn(
(customerId: string, params?: Stripe.CustomerUpdateParams) =>
Promise.resolve({
id: customerId,
currency: 'usd',
...params,
}),
),
create: jest.fn((params?: Stripe.CustomerCreateParams) =>
Promise.resolve({
id: `cust_${faker.lorem.word(10)}`,
...params,
}),
),
del: jest.fn(() => Promise.resolve()),
},
subscriptions: {
list: jest.fn(() =>
Promise.resolve({
data: [],
}),
),
retrieve: jest.fn((subscriptionId: string) =>
Promise.resolve({
id: subscriptionId,
object: 'subscription',
} as Stripe.Response<Stripe.Subscription>),
),
create: jest.fn((params: Stripe.SubscriptionCreateParams) =>
Promise.resolve({
id: `sub_${faker.lorem.word(10)}`,
object: 'subscription',
...params,
}),
),
update: jest.fn(
(subscriptionId: string, params?: Stripe.SubscriptionUpdateParams) =>
Promise.resolve({
id: subscriptionId,
object: 'subscription',
...params,
}),
),
del: jest.fn(() => Promise.resolve()),
},
invoices: {
list: jest.fn(() =>
Promise.resolve([
{
id: `invoice_${faker.lorem.word(10)}`,
},
{
id: `invoice_${faker.lorem.word(10)}`,
},
] as Stripe.Response<Stripe.Invoice>[]),
),
retrieve: jest.fn((subscriptionId: string) =>
Promise.resolve({
id: subscriptionId,
object: 'subscription',
} as Stripe.Response<Stripe.Subscription>),
),
retrieveUpcoming: jest.fn(
(params?: Stripe.InvoiceRetrieveUpcomingParams) =>
Promise.resolve({
...params,
} as Stripe.Response<Stripe.Invoice>),
),
create: jest.fn((params?: Stripe.InvoiceCreateParams) =>
Promise.resolve({
id: `invoice_${faker.lorem.word(10)}`,
...params,
}),
),
pay: jest.fn(() => Promise.resolve()),
},
testHelpers: {
testClocks: {
create: jest.fn((params: Stripe.TestHelpers.TestClockCreateParams) =>
Promise.resolve({
id: `test_clock_${faker.lorem.word(10)}`,
...params,
}),
),
},
},
};
};
- Create a mock of the
nestjs-stripemodule
// __mocks__/nestjs-stripe.mock.ts
import { DynamicModule, Inject, Module } from '@nestjs/common';
import { mockStripe } from './stripe.mock';
@Module({})
class MockStripeModule {
public static forRootAsync(): DynamicModule {
return {
module: MockStripeModule,
providers: [{ provide: 'StripeToken', useFactory: mockStripe }],
exports: [{ provide: 'StripeToken', useFactory: mockStripe }],
};
}
}
export const mockNestJsStripe = () => ({
InjectStripe: () => Inject('StripeToken'),
StripeModule: MockStripeModule,
});
- Define this mock at the start of your
.spec.tsfiles
// some-test.spec.ts
import { mockNestJsStripe } from '@mocks/nestjs-stripe.mock';
jest.mock('nestjs-stripe', () => mockNestJsStripe());