#Nest TestingModule failing on internals of mocked provider module

29 messages · Page 1 of 1 (latest)

toxic mica
#

I'm auto mocking the provider that gets injected into the testing module (PaymentProvider), but when I run a test the test fails by saying a ConfigService cannot find an env variable. The config service is a dependency of the PaymentProviderAdapter which is in turn a dependency of PaymentProvider. So think something like this: new PaymentProvider(new PaymentProviderAdapter(new ConfigService))). I thought that calling jest.mock("@payment-providers/providers/some.provider") would auto-mock all the internals, but it seems like it's still calling the actual adapter, ergo the real config service.

side note: if i include jest.mock("@payment-providers/adapters/some.adapter") -- mocking the adapter module, the tests pass. But i do not see why this should be necessary.

import { Test } from '@nestjs/testing';

import { CustomersModule } from '@customers/customers.module';
import { LoggingModule } from '@logging/logging.module';
import { PaymentProvider } from '@payment-providers/providers/some.provider';

import { CheckoutService } from '../checkout.service';

import type { TestingModule } from '@nestjs/testing';

jest.mock("@payment-providers/providers/some.provider")
jest.mock("@customers/services/customers.service", () => ({
  // eslint-disable-next-line @typescript-eslint/naming-convention
  CustomersService: jest.fn().mockImplementation(() => ({
    getCustomerByAccountId: jest.fn().mockResolvedValue({
      id: "customer_id_MOCK",
    })
  }))
}))

describe('CheckoutService', () => {
  let service: CheckoutService;
  let paymentProvider: PaymentProvider;

  beforeAll(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [CustomersModule, LoggingModule],
      providers: [
        CheckoutService,
        PaymentProvider, // <-- This seems to be the issue
      ],
    }).compile();

    service = module.get<CheckoutService>(CheckoutService);
    paymentProvider = module.get(PaymentProvider)
  });
topaz mango
#

Rather than importing the CustomersModule and LoggingModule, you should provide mock providers of the CustomersService and LoggingService that will be used in the test instead

#

Personally, I haven't found a need for jest.mock when it comes to writing tests with @nestjs/testing, because I can create whatever mock is going to be injected in the test file itself

toxic mica
#

Hmm, I could manually create a mock, but i feel like supplying the TestingModule with providers: [PaymentProvider] should access the mock version if i'm using jest.mock 🤔

#

Also, good point about the modules!

topaz mango
#

I would bet it's not actually PaymentProvider that's causing the issue here. but without seeing what these classes look like, or the explicit error and stack trace, I'd have no real way to tell

toxic mica
#

hmm i might not be understanding all of what jest mock does then

#

because i feel like it shouldn't matter what those internal classes look like aside from top level methods that get mocked

#

but essentially stacktrace is configService.get -> adapter constructor -> payment provider constructor

topaz mango
#

What does CustomsModule and LoggingModule look like? Or what's the explicit error you're receiving?

toxic mica
#

The error is being thrown from the PaymentProvider itself

#

rather a dependency of a dependency of PaymentProvider

topaz mango
#

Can you provide a reproduction, or something more concrete about what is happening here?

toxic mica
#

sure one sec

#

this is the specific error:

FAIL unit src/checkout/services/test/checkout.service.spec.ts
  ● CheckoutService › should be defined

    Missing Payment FLAT MONTHLY FEE PRICE ID

      24 |     const basePriceId = configService.get<string>("FLAT_FEE")
      25 |     if (!basePriceId) {
    > 26 |       throw new Error('Missing Payment FLAT FEE');
         |             ^
      27 |     }
      28 |     const meteredPriceId = configService.get<string>("METERED_PRICE")
      29 |     if (!meteredPriceId) {

      at new PaymentProviderAdapter (src/payment-providers/adapters/paymentProvider.adapter.ts:26:13)
#

lemme get the implementations

#
export class PaymentProvider implements IPaymentProvider {
  private readonly _configService: ConfigService;
  private readonly _paymentAdapter: PaymentProviderAdapter
  private readonly _paymentClient: Client;
  private readonly _webhookSecret: string;

  public constructor(configService: ConfigService, subscriptionsService: SubscriptionsService) {
    const key = configService.get<string>("KEY");
    if (!key) {
      throw new Error('Missing KEY');
    }

    const webhookKey = configService.get<string>("WEBHOOK_KEY")
    if (!webhookKey ) {
      throw new Error('Missing WEBHOOK key');
    }

    this._configService = configService;
    this._paymentAdapter= new PaymentProviderAdapter(configService, subscriptionsService);
    this._webhookSecret= webhookKey ;
    this._paymentClient= new Stripe(key );
  }
#

so that's the provider that i'm using jest.mock on in the checkout.service.spec.ts file.

#

the error occurs from that this._paymentAdapter= new PaymentProviderAdapter(configService, subscriptionsService);

topaz mango
#

From what I'm reading in Jest's docs, I don't think the constructor of that class is mocked, just all of the methods of the class

toxic mica
#

oooh maaaaan

#

do you have a link?

toxic mica
#

gawwwd, i'm very familiar with creating new manual mocks like that, but had no idea that the auto mock functionality didn't provide a mock constructor function

topaz mango
#

Seems like a lot of work that the constructor is doing already. Any chance you could move that stuff to factory providers?

#

Personally, any time I see new for classes that aren't ORM/ODM related, I try to figure out if they could be providers that are injected into other provider. Seems to help keep logic down and coupling looser

toxic mica
#

yeah, that is something i pointed out in the PR (this isn't my code, i just got sucked into debugging the test) and we're going to fix it, since this provider is doing a lot more now than its initial intent.

#

also thinking of maybe removing the config service and having the values be provided... not sure yet

#

anyway, thank you @topaz mango !