Create the class with fakes for its collaborators, then test it only through its public methods.
Steps:
- Make fakes. Typed mocks are easy:
const repo = { findById: jest.fn(), save: jest.fn() } as jest.Mocked<OrderRepository>. Or write a small in-memory class, such asInMemoryOrderRepository. - Construct directly:
new OrdersService(repo, payments, mailer). For NestJS wiring, useTest.createTestingModulewith{ provide: PaymentGateway, useValue: payments }. - Arrange, act, assert. Set what the fakes return, call
service.placeOrder(...), then check the result and the important calls, likepayments.chargebeing called with the right amount.
Good habits:
- Don't test private methods. Test the behaviour they produce.
- Assert outcomes (returned value, saved state) more than every call made.
- Mock only the collaborators that do input/output (database, HTTP, email). Use real value objects like
Money. - If a class needs eight fakes, it probably does too much. Split it.
- Constructor injection lets tests pass fakes for each collaborator.
- Fakes can be typed mocks or small in-memory implementations.
- Test through public methods and check outcomes, not private methods.
- Mock input/output collaborators, but use real value objects.
- Needing many fakes is a sign the class does too much.
Read more