
For years, Automock was a popular framework for defining mocks and stubs in backend test environments. As technology has evolved, new methods and techniques for streamlining the simulation of dependencies in testing have emerged. That's why Automock has been succeeded by Suites, a more modern and robust library.
In this article, we'll explore the transition from Automock to Suites, understand what Suites offers, and see it in action in NestJS through a complete example.
Let's dive into how to use Suites for simplified unit test mocks and stubs!
From Automock to Suites for Node
Automock was long considered a go-to tool for making unit testing easier by creating mocks and stubs within dependency injection frameworks. As development practices evolved, the need for more advanced and flexible testing solutions grew. This is why, on 13 July 2024, the team behind Automock decided to replace it with Suites.
Suites is the rebranded and enhanced successor to Automock. Built on Automock's solid foundation, Suites offers more features and advanced capabilities to meet modern software testing needs.
What is Suites?
Suites is a flexible and opinionated testing meta-framework designed to enhance the testing experience in backend systems.
A meta-framework operates as a "framework of frameworks." Unlike traditional frameworks that offer specific functionalities, a meta-framework like Suites works at a higher level. It works, orchestrates, and enhances various underlying frameworks, libraries, and tools without directly providing the functionality itself.
Specifically, Suites integrates with a few dependency injection frameworks and testing libraries, including NestJS, Jest, and Sinon. The end goal of this testing tool is to simplify the process of creating reliable tests, thus contributing to the development of high-quality software.
At the time of writing, Suites has 428 stars on GitHub and over 37,000 weekly npm downloads. It is gaining strong traction among the community.
Suites for Node: Main Features and Capabilities
Now that you understand what Suites is and how it represents the future of Automock, let's explore what it has to offer!
Multiple Dependency Injection and Mocking Adapters
Suites supports the following DI frameworks via dedicated adapter packages:
DI framework | Adapter name |
---|---|
NestJS | @suites/di.nestjs |
Inversify | @suites/di.inversify |
Note: The TSyringe adapter is currently under development.
Similarly, Suites integrates with the following mocking libraries:
Mocking library | Adapter name |
---|---|
Jest | @suites/doubles.jest |
Sinon | @suites/doubles.sinon |
Vitest | @suites/doubles.vitest |
Note: Support for Bun and Deno is coming soon.
Support for Both Solitary and Sociable Unit Tests
In testing, a "unit" refers to the smallest testable part of an application. Depending on the software design, that can be a function, method, procedure, module, object, or class. In Suites, a unit can be tested either in isolation or in combination with its dependencies.
These two approaches to unit testing in Suites are:
- Solitary unit testing: Also known as isolated unit testing, these tests focus on testing a single unit of work entirely separate from its external dependencies. They use test doubles (i.e., mocks and stubs) to simulate the behavior of the unit's dependencies. The goal is to verify the functionality and reliability of individual units, ensuring they perform as expected under controlled conditions. This approach is supported by the
TestBed.solitary()
method. - Sociable unit testing: Also known as integrated unit testing, these tests focus on testing a unit of work in conjunction with its real dependencies. While they still mock the dependencies of the unit's dependencies, they ensure that the interactions between a unit and its immediate collaborators are tested in a controlled environment. This provides a broader scope of validation compared to solitary unit tests and is supported by the
TestBed.sociable()
method.
Zero-Setup Mocking
Suites provides a comprehensive API for generating mock objects, eliminating the need for manual setup and reducing boilerplate code.
The TestBed.solitary()
and TestBed.sociable()
methods in Suites offer advanced mocking capabilities through these two methods:
mock().final()
: To set the final behavior of a mock, meaning that the mock cannot be changed later for further stubbing or assertions. It defines how the mock should behave without returning a reference for future adjustments.
const { unit } = await TestBed.solitary(UsersService) .mock(UsersRepository) // stub the getFirst() method of UsersRepository .final({ getFirst: async () => ({ id: 1, name: "John Doe" }) }) .compile();
mock().impl()
: To define mock behavior using a callback function. It returns a reference to the mocked unit, allowing further stubbing.
const { unit, unitRef } = await TestBed.solitary(UsersService) .mock(UsersRepository) // define how to stub the getFirst() method of UsersRepository .impl(stubFn => ({ getFirst: stubFn().mockResolvedValue({ id: 1, name: 'John Doe' }) .compile();
In particular, unit
represents the instance of the class under test.
Instead, unitRef
is the mocked dependency created by the TestBed
.
In other terms, unitRef
is a stubbed instance of a mocked dependency that you can further customize using methods provided by your configured mocking library.
Typed Mocks in TypeScript
Suites comes with native TypeScript integration, enabling you to retain the same types as real objects while mocking.
Specifically, the Mocked
type from the @suites/unit
package automatically types the mocked instances of classes.
Here's Mocked
in action:
import { TestBed, Mocked } from "@suites/unit"; import { UserService } from "./user.service"; import { UserRepository } from "./user.repository"; describe("Users Service Unit Spec", () => { let usersService: UsersService; // the object to mock let usersRepository: Mocked<UsersRepository>; beforeAll(async () => { const { unit, unitRef } = await TestBed.solitary(UsersService).compile(); usersService = unit; // the mocked dependency will have the right type usersRepository = unitRef.get(UsersRepository); }); it("should return the user name and call repository", async () => { // the mocked dependency comes with mocking functions // from your mocking library usersRepository.getUserById.mockResolvedValue({ id: 14, name: "Maria Williams", }); const result = await usersService.getUserName(14); expect(usersRepository.getUserById).toHaveBeenCalledWith(14); expect(result).toBe("Maria Williams"); }); });
Thanks to Mocked
, you don't have to define a custom type for the usersRepository
mocked dependency.
Optimized Performance
Suites bypasses the traditional dependency injection container by directly leveraging the dependency injection (DI) framework's reflection and metadata capabilities to set up an isolated test environment. Instead of loading the entire DI container, Suites creates a lightweight, virtual container that mirrors the mechanism.
This streamlined approach simplifies setup, reduces overhead, and speeds up test execution. All that while preserving the benefits of dependency injection principles.
Backward Compatibility With Automock
Suites represents an evolution from Automock, which means the transition requires minimal effort.
The main change involves moving the TestBed
factory, which is now part of @suites/unit
rather than being split between @suites/jest
or @suites/sinon
.
The key API changes to be aware of when migrating from Automock to Suites are:
TestBed.compile()
is nowasync
.TestBed.create()
has been renamed toTestBed.solitary()
.- The new
sociable()
method has been added to theTestBed
API. TestBed
is now imported from@suites/unit
regardless of the installed adapters.- The new
Mocked
type from@suites/unit
offers deep partial mock capabilities. - The
mock.using()
method has been replaced by themock.impl()
andmock.final()
methods.
Explore the documentation for a complete Automock to Suites migration guide.
Using Suites for Unit Testing in NestJS
Now you'll learn how to use Suites for mocking within a Jest unit test in NestJS.
For a quicker setup or to have the code readily available, clone the GitHub repository supporting this article:
git clone https://github.com/Tonel/nestjs-suites-demo
Navigate to the project folder in your terminal and install the project dependencies:
cd nestjs-suites-demo npm install --legacy-peer-deps
Note that the --legacy-peer-deps
option is required to avoid the "unable to resolve dependency tree" you may get while installing Suites and its dependencies.
Creating a NestJS Project
Before getting started, make sure you have Node.js and the NestJS CLI installed on your machine.
Then, initialize a new NestJS project using the new
command:
nest new nestjs-suites-demo
Great! The nestjs-suites-demo
directory now contains a new NestJS project with a Jest integration.
Load it into your favorite JavaScript IDE.
You're ready to define the CRUD APIs for handling products in an in-memory database.
First, add a new module to the NestJS project with the generate module
command:
nest generate module products
This will create a products.module.ts
file in the products
folder inside /src
:

You're ready to create a new entity representing your product data. Create an entities
directory inside products
and add the following product.entity.ts
file:
export interface Product { id: string; name: string; description: string; price: number; }
This defines a Product
entity with id
, name
, description
, and price
fields.
Now, create a service for your Product
entity. In the products
folder, add a products.service.ts
file as follows:
import { Injectable } from "@nestjs/common"; import { Product } from "./entities/product.entity"; @Injectable() export class ProductsService { // the in-memory database where to store data private products: Product[] = []; create(product: Omit<Product, "id">): Product { const newProduct: Product = { // generate a random UUID for the new product id: crypto.randomUUID(), ...product, }; this.products.push(newProduct); return newProduct; } findAll(): Product[] { return this.products; } findById(id: string): Product | undefined { return this.products.find((product) => product.id === id); } update( id: string, updateProduct: Partial<Omit<Product, "id">> ): Product | undefined { const product = this.findById(id); if (product) { Object.assign(product, updateProduct); return product; } return undefined; } delete(id: string): boolean { const index = this.products.findIndex((product) => product.id === id); if (index !== -1) { this.products.splice(index, 1); return true; } return false; } }
The above class defines the business logic for implementing CRUD operations to manage products. In this example, the in-memory database is simply a JavaScript array.
Next, add the following products.controller.ts
file to the products
folder:
import { Controller, Get, Post, Put, Delete, Param, Body, HttpCode, HttpStatus, NotFoundException, } from "@nestjs/common"; import { ProductsService } from "./products.service"; import { Product } from "./entities/product.entity"; @Controller("products") export class ProductsController { constructor(private productsService: ProductsService) {} @Post() @HttpCode(HttpStatus.CREATED) async create( @Body() createProductDto: Omit<Product, "id"> ): Promise<Product> { return this.productsService.create(createProductDto); } @Get() async getAll(): Promise<Product[]> { return this.productsService.findAll(); } @Get(":id") async getOne(@Param("id") id: string): Promise<Product> { const product = this.productsService.findById(id); if (!product) { throw new NotFoundException(`Product with ID ${id} not found`); } return product; } @Put(":id") async update( @Param("id") id: string, @Body() updateProductDto: Partial<Omit<Product, "id">> ): Promise<Product> { const updatedProduct = this.productsService.update(id, updateProductDto); if (!updatedProduct) { throw new NotFoundException(`Product with ID ${id} not found`); } return updatedProduct; } @Delete(":id") @HttpCode(HttpStatus.NO_CONTENT) async delete(@Param("id") id: string): Promise<void> { const deleted = this.productsService.delete(id); if (!deleted) { throw new NotFoundException(`Product with ID ${id} not found`); } } }
The ProductsController
class provides the implementation for the route methods below:
create()
: To create a new product. Mapped to thePOST
/products
endpoint.getOne()
: To get a single product. Mapped to theGET
/products/:id
endpoint.getAll()
: To get all products. Mapped to theGET
/products
endpoint.update()
: To update a single product. Mapped to thePUT
/products/:id
endpoint.delete()
: To delete a single product. Mapped to theDELETE
/products/:id
endpoint.
Each of the above routes calls the corresponding CRUD method in the ProductsService
class.
Register the ProductsController
and ProductsService
files in products.module.ts
with the @Module
annotation:
import { Module } from "@nestjs/common"; import { ProductsController } from "./products.controller"; import { ProductsService } from "./products.service"; @Module({ controllers: [ProductsController], providers: [ProductsService], }) export class ProductsModule {}
Here we go! Your NestJS backend now exposes CRUD endpoints to create, read, update, and delete products.
Run your NestJS application locally with:
npm run start:dev
Your backend server will start on port 3000
.
Add some products by calling the POST
/products
endpoint:
curl -X POST http://localhost:3000/products \ -H "Content-Type: application/json" \ -d '{ "name": "UltraWidget", "description": "A versatile gadget for everyday tasks.", "price": 199.99 }'
Then, retrieve them with a GET
request to /products
:
curl -X GET http://localhost:3000/products
The result will be something like:
[ { "name": "UltraWidget", "description": "A versatile gadget for everyday tasks.", "price": 199.99, "id": "ce34a968-1a73-4b82-99fc-9af57768c22d" }, { "name": "SmartLight 3000", "description": "An energy-efficient smart light bulb with customizable colors.", "price": 29.99, "id": "080c044f-3dfc-43e0-90d9-9666086ff944" }, { "name": "EcoBreeze Air Purifier", "description": "A compact air purifier that removes 99.9% of airborne particles.", "price": 149.95, "id": "a9188527-f139-482d-bd01-ff501c168c23" } ]
Amazing! You now have a NestJS application to unit test with Suites.
Setting Up Suites
Install the Suites core package by adding the @suites/unit
npm package to your project's development dependencies:
npm install --save-dev @suites/unit
To fully integrate Suites, you also need to install the framework and mocking library adapters for Jest and NestJS:
npm install --save-dev @suites/doubles.jest @suites/di.nestjs
Suites will automatically detect the installed adapters and configure itself accordingly.
If you're an npm user, you may get an "unable to resolve dependency tree" error due to the reflect-metadata
peer package.
As a quick fix, launch the installation command with the --legacy-peer-deps
option:
npm install --save-dev @suites/doubles.jest @suites/di.nestjs --legacy-peer-deps
For more details, check out the dedicated guide on how to properly address that error.
As a final step, ensure that your tsconfig.json
file has the emitDecoratorMetadata
and experimentalDecorators
options set to true
:
{ "compilerOptions": { "emitDecoratorMetadata": true, "experimentalDecorators": true } }
Suites requires these configurations to reflect class dependencies and operate properly with dependency injection frameworks.
Fantastic! It's time to define your first unit test in Suites.
Defining a Unit Test
When adding test files to a NestJS project, the best practice is to place them as close as possible to the files they're testing. This keeps your test files organized and easy to find.
Suppose you want to test the getAll()
method of ProductsController
in isolation.
As a first step, create a products.controller.spec.ts
file within the products
folder.
After adding the test file, your products
directory in the NestJS project will look like this:

Now, time to define the testing logic.
You can use Suites to verify that the getAll()
method works in isolation with a Jest unit test:
import { TestBed, Mocked } from "@suites/unit"; import { ProductsController } from "./products.controller"; import { ProductsService } from "./products.service"; import { Product } from "./entities/product.entity"; describe("Products Controller Unit Spec", () => { // declare the unit under test let productsController: ProductsController; // declare a mock dependency let productsService: Mocked<ProductsService>; beforeAll(async () => { // create an isolated test environment for the unit under test const { unit, unitRef } = await TestBed.solitary( ProductsController ).compile(); // assign the unit to test productsController = unit; // assign the dependency to mock from the unit reference productsService = unitRef.get(ProductsService); }); test("should return 3 products", async () => { // the products you expect to retrieve from the database const mockedProducts: Product[] = [ { name: "UltraWidget", description: "A versatile gadget for everyday tasks.", price: 199.99, id: "ce34a968-1a73-4b82-99fc-9af57768c22d", }, { name: "SmartLight 3000", description: "An energy-efficient smart light bulb with customizable colors.", price: 29.99, id: "080c044f-3dfc-43e0-90d9-9666086ff944", }, { name: "EcoBreeze Air Purifier", description: "A compact air purifier that removes 99.9% of airborne particles.", price: 149.95, id: "a9188527-f139-482d-bd01-ff501c168c23", }, ]; // mock the findAll() method so that it returns // the expected data productsService.findAll.mockReturnValue(mockedProducts); // call the function to test const products = await productsController.getAll(); // verify that the mocked service method // has been called as expected expect(productsService.findAll).toHaveBeenCalled(); // verify that it return the expected data expect(products).toEqual(mockedProducts); }); });
In the snippet above, TestBed
creates an isolated test environment for solitary unit testing of ProductsController
.
This gives you the ability to test the controller independently of other parts of the application.
To do so, you must mock the ProductsService
class used by ProductsController
.
That allows you to interact with a mock version of the actual service, ensuring that the test is focused on the controller logic rather than external dependencies.
In particular, you can use the mockReturnValue()
method to mock what data the findAll()
method from ProductsService
should return.
When calling the getAll()
method of ProductsController
, you can now verify that it works as expected with a few assertions.
You just wrote a Jest unit test with Suites integration in NestJS.
Running the Test
Run your Jest unit tests with this command:
npx jest
Or, equivalently, execute:
npm run test
The test
npm script is automatically added to package.json
by the nest new
utility.
Also, don't forget that the nest new
command configures Jest in your project.
So, you don't have to worry about installing and setting it up manually.
The result should be something similar to:
PASS src/app.controller.spec.ts (6.009 s) PASS src/products/products.controller.spec.ts (6.78 s) Test Suites: 2 passed, 2 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 7.568 s Ran all test suites.
Ignore the src/app.controller.spec.ts
test file, as that's automatically generated by nest new
when creating a new project.
Instead, focus on the products.controller.spec.ts
file, which contains the Jest unit test of interest.
Et voilà ! Your unit tests with Suites mocking logic work like a charm.
Wrapping Up
In this blog post, we explored how Suites has taken the place of Automock by extending its capabilities with more features and better support for modern testing.
You now know:
- Why Automock was replaced with Suites
- What Suites is and how it enhances mocking
- The features and characteristics that Suites offers for mocking and stubbing
- How to use Suites in NestJS unit tests
Thanks for reading!
Wondering what you can do next?
Finished this article? Here are a few more things you can do:
- Subscribe to our JavaScript Sorcery newsletter and never miss an article again.
- Start monitoring your JavaScript app with AppSignal.
- Share this article on social media
Most popular Javascript articles
Top 5 HTTP Request Libraries for Node.js
Let's check out 5 major HTTP libraries we can use for Node.js and dive into their strengths and weaknesses.
See moreWhen to Use Bun Instead of Node.js
Bun has gained in popularity due to its great performance capabilities. Let's see when Bun is a better alternative to Node.js.
See moreHow to Implement Rate Limiting in Express for Node.js
We'll explore the ins and outs of rate limiting and see why it's needed for your Node.js application.
See more

Antonello Zanini
Guest author Antonello is a software engineer, but prefers to call himself a Technology Bishop. Spreading knowledge through writing is his mission.
All articles by Antonello ZaniniBecome our next author!
AppSignal monitors your apps
AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!
