Skip to main content

Mock Configuration

Suites provides two methods for configuring mock behavior: .mock().final() for immutable configurations and .mock().impl() for flexible implementations.

Methods

.mock().final()

Creates an immutable mock configuration. Once set, the mock cannot be retrieved or modified.

Signature

mock<D>(dependency: Type<D> | string | symbol)
.final(implementation: Partial<D>): TestBuilder

Parameters

ParameterTypeDescription
dependencyType<D> | string | symbolThe dependency to mock
implementationPartial<D>Object with method implementations

Example

const { unit } = await TestBed.solitary(UserService)
.mock(UserApi)
.final({
getRandom: async () => ({ id: 1, name: "John" }),
getAll: async () => [{ id: 1, name: "John" }]
})
.compile();

// Note: UserApi cannot be retrieved from unitRef

.mock().impl()

Creates a flexible mock configuration using the underlying mock library (Jest, Vitest, etc.).

Signature

mock<D>(dependency: Type<D> | string | symbol)
.impl(factory: (stubFn: () => MockFunction) => Partial<D>): TestBuilder

Parameters

ParameterTypeDescription
dependencyType<D> | string | symbolThe dependency to mock
factory(stubFn) => Partial<D>Factory function receiving the mock library's stub function

Example

const { unit, unitRef } = await TestBed.solitary(UserService)
.mock(UserApi)
.impl(stubFn => ({
getRandom: stubFn().mockResolvedValue({ id: 1, name: "John" }),
getAll: stubFn().mockReturnValue([])
}))
.compile();

// UserApi can still be retrieved and modified
const userApi = unitRef.get(UserApi);
userApi.getRandom.mockResolvedValue({ id: 2, name: "Jane" });

Comparison

Feature.mock().final().mock().impl()
Retrievable via unitRef❌ No✅ Yes
Can modify after compile❌ No✅ Yes
Access to mock library features❌ No✅ Yes
Use caseFixed test dataDynamic behavior

Examples

Using .mock().final() for Fixed Data

Perfect for tests with predictable, unchanging data:

describe("UserService with fixed data", () => {
let userService: UserService;

beforeAll(async () => {
const { unit } = await TestBed.solitary(UserService)
.mock(Database)
.final({
findUser: async (id: string) => ({ id, name: "Test User" }),
saveUser: async (user: User) => ({ ...user, id: "123" })
})
.compile();

userService = unit;
});

it("should always return Test User", async () => {
const user = await userService.getUser("any-id");
expect(user.name).toBe("Test User");
});
});

Using .mock().impl() for Dynamic Behavior

Better for tests requiring different responses or Jest/Vitest-specific features:

describe("PaymentService with dynamic mocks", () => {
let paymentService: PaymentService;
let gateway: Mocked<PaymentGateway>;

beforeAll(async () => {
const { unit, unitRef } = await TestBed.solitary(PaymentService)
.mock(PaymentGateway)
.impl(stubFn => ({
charge: stubFn(), // Don't set behavior yet
refund: stubFn().mockResolvedValue({ status: "success" })
}))
.compile();

paymentService = unit;
gateway = unitRef.get(PaymentGateway);
});

it("should handle successful payment", async () => {
gateway.charge.mockResolvedValueOnce({ status: "success" });
const result = await paymentService.process(100);
expect(result.status).toBe("success");
});

it("should handle failed payment", async () => {
gateway.charge.mockRejectedValueOnce(new Error("Insufficient funds"));
await expect(paymentService.process(100))
.rejects.toThrow("Insufficient funds");
});
});

Multiple Mock Configurations

You can chain multiple mock configurations:

const { unit, unitRef } = await TestBed.solitary(OrderService)
.mock(Database)
.final({
getOrder: async (id: string) => ({ id, items: [] })
})
.mock(EmailService)
.impl(stubFn => ({
send: stubFn().mockResolvedValue({ sent: true })
}))
.mock(Logger)
.impl(stubFn => ({
log: stubFn(),
error: stubFn()
}))
.compile();

// Can retrieve EmailService and Logger, but not Database
const emailService = unitRef.get(EmailService);
const logger = unitRef.get(Logger);

Framework Examples

NestJS

@Injectable()
class NotificationService {
constructor(
private mailer: MailerService,
private template: TemplateService
) {}
}

const { unit, unitRef } = await TestBed.solitary(NotificationService)
.mock(MailerService)
.impl(stubFn => ({
send: stubFn().mockResolvedValue({ messageId: "123" })
}))
.mock(TemplateService)
.final({
render: async (name: string) => `<h1>Hello ${name}</h1>`
})
.compile();

InversifyJS

@injectable()
class OrderService {
constructor(
@inject(TYPES.Database) private db: Database,
@inject(TYPES.Logger) private logger: Logger
) {}
}

const { unit, unitRef } = await TestBed.solitary(OrderService)
.mock(TYPES.Database)
.impl(stubFn => ({
save: stubFn().mockResolvedValue({ id: "123" })
}))
.mock(TYPES.Logger)
.final({
info: () => {},
error: () => {}
})
.compile();

Best Practices

When to Use .mock().final()

  • Static test data that won't change
  • Simple stub implementations
  • When you don't need Jest/Vitest-specific features
  • Ensuring mock behavior can't be accidentally modified

When to Use .mock().impl()

  • Different responses per test case
  • Using Jest/Vitest matchers (toHaveBeenCalledWith, etc.)
  • Complex mock behavior (.mockImplementationOnce, etc.)
  • When you need to spy on method calls

Common Patterns

Partial Mocking

Both methods support partial mocking - you only need to implement the methods you use:

// Only mock the methods you need
.mock(UserRepository)
.impl(stubFn => ({
findById: stubFn().mockResolvedValue(testUser)
// Other methods remain undefined
}))

Error Simulation

// With .impl()
.mock(ApiClient)
.impl(stubFn => ({
fetch: stubFn()
.mockRejectedValueOnce(new Error("Network error"))
.mockResolvedValueOnce({ data: "Success" })
}))

// With .final()
.mock(ApiClient)
.final({
fetch: async () => {
throw new Error("Network error");
}
})

See Also