Core Testing
Services are TypeScript classes, but most services are designed to be resolved by a container. Test simple services directly when their constructor and methods do not need Wirestate. Use a fresh container when dependency injection, lifecycle or message buses are part of the behavior.
No Container
import { LoggerService } from "./LoggerService";
test("logs a message", () => {
const logger = new LoggerService();
const spy = jest.spyOn(console, "log").mockImplementation(() => {});
logger.log("hello");
expect(spy).toHaveBeenCalledWith("[log] hello");
spy.mockRestore();
});One Service
Use container.bind with a fresh Container when one service needs Wirestate wiring.
import { Container } from "@wirestate/core";
import { CounterService } from "./CounterService";
test("increments count", () => {
const service = new Container().bind(CounterService).get(CounterService);
service.increment();
expect(service.count).toBe(1);
});Several Services
new Container(...) binds a group of services. Use activate when activation behavior needs to run before assertions. Call container.provision() whenever the behavior under test relies on messaging: auto-wired @OnEvent, @OnCommand, and @OnQuery handlers subscribe at provision and unsubscribe at deprovision, so without a UI provider a plain-core test must provision the container first. Tear down with container.deprovision() when the container is reused, or container.destroy() when the test is finished with it. destroy() deprovisions first, so it can be called on its own.
import { Container, EventBus, EventsPlugin } from "@wirestate/core";
import { CounterService, LoggerService } from "./services";
test("counter emits event on increment", () => {
const container = new Container({
bindings: [LoggerService, CounterService],
plugins: [new EventsPlugin()],
});
// Subscribes the auto-wired @OnEvent handlers before the event is emitted.
container.provision();
const counter = container.get(CounterService);
const events: Array<string | symbol> = [];
container.get(EventBus).subscribe((event) => events.push(event.type));
counter.increment();
expect(events).toContain("COUNTER_INCREMENTED");
container.deprovision();
});Add Bindings
Use container.bind when a test starts from an existing container and needs one more service or descriptor.
import { Container } from "@wirestate/core";
import { CartService, PricingService } from "./services";
test("cart uses pricing service", () => {
const container = new Container({ bindings: [CartService] });
container.bind(PricingService);
const cart = container.get(CartService);
expect(cart.total()).toBe(25);
});Use container.unbind to remove a binding, deactivating any constructed service.
import { Container } from "@wirestate/core";
const container = new Container({ bindings: [CartService] });
container.bind(PricingService);
container.unbind(PricingService);Replace Dependencies
Bind a constant under the dependency token before resolving the service under test.
import { Container } from "@wirestate/core";
test("cart uses mocked api client", async () => {
const container = new Container({ bindings: [CartService] });
const api = { post: jest.fn().mockResolvedValue({ ok: true }) };
container.bind({ token: ApiClient, value: api as unknown as ApiClient });
const cart = container.get(CartService);
await cart.checkout();
expect(api.post).toHaveBeenCalledWith("/checkout", expect.anything());
});