Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to Mock Stripe Client for Unit Tests #113

Open
markusheinemann opened this issue Nov 4, 2021 · 2 comments
Open

How to Mock Stripe Client for Unit Tests #113

markusheinemann opened this issue Nov 4, 2021 · 2 comments

Comments

@markusheinemann
Copy link

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.

@markusheinemann markusheinemann changed the title How to Mock Stripe Client How to Mock Stripe Client for Unit Tests Nov 4, 2021
@fer8a
Copy link

fer8a commented Feb 8, 2022

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

  1. Mock the StripeToken dependency 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);
});
  1. Mock StripeToken instead of Stripe (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');
});

@agrawal-rohit
Copy link

Here's the way I got it working:

  1. 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,
          }),
        ),
      },
    },
  };
};
  1. Create a mock of the nestjs-stripe module
// __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,
});
  1. Define this mock at the start of your .spec.ts files
// some-test.spec.ts
import { mockNestJsStripe } from '@mocks/nestjs-stripe.mock';
jest.mock('nestjs-stripe', () => mockNestJsStripe());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants