CodePark/ questions / Classes in NestJS & testing
START PRACTICINGPRACTICE
Backend question · Object-oriented programming › Classes in NestJS & testing

In NestJS, why should OrdersService take a PaymentGateway abstraction in its constructor instead of StripeGateway? Show how to wire it with an abstract class.

export abstract class PaymentGateway {
  abstract charge(amount: Money): Promise<ChargeId>;
}

@Injectable()
export class OrdersService {
  constructor(private readonly payments: PaymentGateway) {}
}

// orders.module.ts
providers: [OrdersService, { provide: PaymentGateway, useClass: StripeGateway }]

The service should depend on what it needs (charge a payment), not on which company does it.

Why:

  • Swap without edits: switching to another provider, or a sandbox in staging, changes one line in the module.
  • Easy tests: pass a fake gateway. No Stripe calls, and no network.
  • Clear boundary: Stripe's SDK types stay inside StripeGateway.

Why an abstract class works as the token: Nest reads constructor parameter types from metadata that TypeScript emits. An interface is erased, so it would need @Inject("PAYMENT_GATEWAY"). An abstract class is a real value at runtime. So { provide: PaymentGateway, useClass: StripeGateway } works with no @Inject. StripeGateway can use implements PaymentGateway, so it doesn't inherit anything.

Parameter properties: private readonly payments: PaymentGateway in the constructor declares the field and assigns it in one step.

Note: a NestJS provider is a singleton by default. So the class must not keep per-request data in fields.

  • Depending on an abstraction lets you swap the implementation in one place.
  • Tests can pass a fake implementation with no network calls.
  • Interfaces are erased, so they need a string or symbol token with @Inject.
  • An abstract class exists at runtime, so it works as a token without @Inject.
  • Constructor parameter properties declare and assign the field in one step.