Framework
suites-dev/suites-unit avatar
suites-dev/suites-unit

Suites wires your mocks from the constructor instead of the mock factory

A unit testing framework for TypeScript backends working with inversion of control and dependency injection

546 stars20 forksTypeScriptApache-2.0

At a glance

What is it?
Suites is a unit testing framework for TypeScript backends built on inversion of control and dependency injection, covering NestJS, InversifyJS and plain constructor injection. It reads the class under test, generates the mocks, injects them and hands them back typed, so the test file stops being a list of mock factories.
Who is it for?
Adopt Suites if your TypeScript backend uses constructor injection and your test files are mostly mock factories, since the framework reads the constructor and wires the mocks for you. Skip it if your code resolves dependencies through plain function calls or a hand-rolled container, because the wiring depends on constructor metadata.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem: mocks are written by hand, over and over

The framework description is narrow on purpose: a unit testing framework for TypeScript backends working with inversion of control and dependency injection. The audience is teams whose services are built on a DI container and whose unit tests therefore start by assembling a graph of mocks before a single assertion runs. Suites attacks the assembly step rather than the assertions. Its declarative API is described as producing fully-typed, isolated test environments from a single specification, including automatic mock generation and dependency wiring based on constructor metadata. The type-safe refactoring claim follows from that: mocks are bound to concrete TypeScript implementations, so changes to constructors, injected dependencies and method signatures are validated at compile time. That is the part that matters for a team doing a large refactor, because a mock factory typed as `any` will keep compiling after the class under test changes shape, and the failure then shows up as a runtime error rather than a type error.

Solitary mode and how the wiring happens

Solitary mode tests a single unit in complete isolation, with all dependencies automatically mocked, and the README says to use it when you want to test your unit's logic without any real dependencies. The mechanism is documented in four steps. TestBed analyzes the class, reading the UserService constructor to find its UserApi and Database dependencies. Automatic mocks are created, generating mock instances of UserApi and Database with all methods as stubs. The dependencies are injected, wiring the mocks into the UserService constructor automatically. And type-safe access is provided, with unitRef.get() used to retrieve mocks with full TypeScript support. The setup is one compile call inside a beforeAll block.

ts
  beforeAll(async () => {
    // Create the test environment with automatic mocking
    const testBed = await TestBed.solitary(UserService).compile();

    userService = testBed.unit;
    // Retrieve the mock instances
    userApi = testBed.unitRef.get(UserApi);
    database = testBed.unitRef.get(Database);
  });

The mocks start as stubs with no predefined responses, so a method returns undefined until the test configures it, and the README notes that each method becomes a stub with no predefined responses and that responses are configured in tests as needed. In the example, `userApi.getRandom.mockResolvedValue(mockUser)` sets the stub, and the assertion checks that the unit called `database.saveUser` with the value it received.

Sociable mode: choose which dependencies stay real

Sociable mode tests how components work together, and the difference from solitary mode is a single decision you make: which dependencies to keep real, using `.expose()`, while external I/O remains mocked. The README describes it as the mode to use when you want to test integration between multiple units. The chain is the same shape, with an expose call added before compile.

ts
  beforeAll(async () => {
    // Create the test environment with automatic mocking
    const testBed = await TestBed.solitary(UserService).compile();

    userService = testBed.unit;
    // Retrieve the mock instances
    userApi = testBed.unitRef.get(UserApi);
    database = testBed.unitRef.get(Database);
  });

The distinction between the two modes is worth dwelling on, because it is the one concept a reader has to take away. Solitary mode answers what does this unit do given these inputs. Sociable mode answers what happens when this unit talks to that collaborator, with the boundary of real code placed exactly where you want it. Most suites need both, and the two names are borrowed from the classic unit testing vocabulary of solitary and sociable tests. What the framework adds is that the choice is expressed per dependency rather than per test file, so a single specification can mix a real in-process collaborator with a mocked external service.

Installation and the adapter matrix

The prerequisites are a DI framework and a testing library, and the README is specific about both. The DI framework can be NestJS, InversifyJS, or plain TypeScript classes with constructor injection. The testing library can be Jest, Vitest or Sinon. The unit package installs like any other dev dependency.

bash
npm i -D @suites/unit
# or
yarn add -D @suites/unit
# or
pnpm add -D @suites/unit

Then you install one adapter for your DI framework and one adapter for your testing library. The DI framework adapters are `@suites/di.nestjs` for NestJS and `@suites/di.inversify` for InversifyJS. The testing library adapters begin with `@suites/doubles.jest` for Jest, and the README also names Vitest and Sinon as supported testing libraries. The one-of-each rule is a deliberate design choice and it is the reason the framework can present the same API across NestJS, InversifyJS, Jest, Vitest and Sinon: the core package knows nothing about your container or your mocking library, and the adapter supplies that knowledge. NestJS and InversifyJS are listed with official documentation recipes on their own sites, which is a useful signal about how the maintainers intend the integration to work.

Standardised surface across frameworks, and the AI angle

The third feature in the README is standardisation: a uniform testing surface across NestJS, InversifyJS and other DI setups, so test structure and patterns stay consistent regardless of the framework underneath. That matters in organisations that mix frameworks across services, since it removes the excuse for each team inventing its own test scaffolding. The fourth feature is called AI Ready, and it is worth reading with a certain amount of scepticism because the same reasoning applies to human authors. The claim is that a concise and strict test API minimises the context requirements for LLM-based coding agents, producing higher-quality generated tests, clearer and more actionable error messages that guide automatic self-correction, and a higher likelihood of completing test authoring in a single pass. There is no benchmark in the repository to back that up. What is verifiable is the shape of the API: one compile call, a typed handle to the unit, and a typed handle to each mock. That is a small surface, and small surfaces are easier for a person or a model to reason about.

Repository layout, tooling and release cadence

This repository is the monorepo for the whole family of packages rather than only the unit testing package, and the layout reflects that. There is a packages directory, a pnpm-workspace.yaml, a lerna.json and a pnpm-lock.yaml, with pnpm pinned as the package manager at 8.15.9 in the root package.json. The scripts cover build, lint, test and e2e, where e2e runs local-e2e.ts through ts-node, and there is a separate script that validates TSDoc links. A husky directory with a prepare script, a .lintstagedrc.json running eslint on TypeScript files, .prettierrc and eslint.config.mjs cover the code style, and codecov.yml is present along with jest.base.config.ts and jest.config.ts for the test runner itself. A local-e2e.docker-compose.yaml is used for end-to-end runs. There is a docs directory, a CODE_OF_CONDUCT.md, a SECURITY.md, a CONTRIBUTING.md and a JSDOC_STYLE_GUIDE.md. Releases are at v3.1.1 from 2026-05-08, v3.1.0 from 2026-05-05 and v3.0.1 from 2026-05-05, and the last push to the repository was on 2026-10-13. The licence is Apache-2.0, with the badge in the README pointing at the Apache 2.0 text and the field declared in the root package.json.

Where Suites does not help

There are clear boundaries. The first is that the whole approach rests on constructor metadata. If your service resolves dependencies through plain function calls, a service locator, or a hand-rolled container that does not expose the constructor signature, there is nothing for the framework to read, and you are back to writing mocks by hand. The second is that the framework is a unit testing framework. It says nothing about integration or end-to-end testing, and while the repository does have e2e infrastructure for its own packages, that is testing Suites rather than a testing strategy for your application. The third is the adapter matrix. Only NestJS and InversifyJS are listed as DI framework adapters, so a team on TypeDI, tsyringe, or a bespoke container gets nothing. The fourth is the cost of the abstraction. Every mock you retrieve is a mock the framework created and wired, which means the test file no longer tells you where the object graph came from, and a failing test now requires understanding the compile step as well as the assertion. For a codebase where the wiring is the interesting part, that trade can go the wrong way.

Editorial conclusion

Adopt Suites if your TypeScript backend uses constructor injection and your test files are mostly mock factories, since the framework reads the constructor and wires the mocks for you. Skip it if your code resolves dependencies through plain function calls or a hand-rolled container, because the wiring depends on constructor metadata. Before you install, check the adapter matrix: you need one DI framework adapter and one testing library adapter, and the README lists NestJS and InversifyJS on one side and Jest, Vitest and Sinon on the other.

Frequently asked questions

What is Suites (Unit)?

It is a unit testing framework for TypeScript backends working with inversion of control and dependency injection. It provides a declarative API for fully-typed, isolated test environments from a single specification, including automatic mock generation and dependency wiring based on constructor metadata.

What is the difference between Solitary and Sociable mode in Suites?

Solitary mode tests a single unit with all dependencies automatically mocked, while Sociable mode keeps chosen dependencies real using `.expose()` and leaves external I/O mocked, so you can test how components work together.

How do I install Suites and its adapters?

Install the unit package with `npm i -D @suites/unit` (or yarn or pnpm), then install one DI framework adapter, `@suites/di.nestjs` or `@suites/di.inversify`, and one testing library adapter such as `@suites/doubles.jest`.

Which DI frameworks and testing libraries does Suites support?

The prerequisites list NestJS, InversifyJS, or plain TypeScript classes with constructor injection as DI options, and Jest, Vitest or Sinon as testing libraries. NestJS and InversifyJS each have an official recipe linked from their own documentation sites.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. suites-dev/suites-unit on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/suites-dev-suites-unit.svg)](https://hysenlabs.com/projects/suites-dev-suites-unit)

Was this useful?