Quick Start
In this guide, we'll build a small, two-class application and test it using Suites. While the example uses Jest and NestJS annotations for convenience, Suites supports a wide range of dependency injection (DI) frameworks and mocking libraries. The principles remain the same, so you can adapt this guide to your preferred tools.
π‘ You can find this example and more in the Suites Examples repository
Setupβ
First, let's set up our project. Ensure you have Node.js installed and initialize a new project:
mkdir suites-quickstart
cd suites-quickstart
npm init -y
Install the necessary Suites packages:
npm install --save-dev @suites/unit @suites/di.nestjs @suites/doubles.jest
Install additional packages for TypeScript and Jest:
npm install --save-dev ts-jest @types/jest jest typescript
And lastly, install the reflect-metadata package:
npm install reflect-metadata
Project Structureβ
suites-quickstart/
βββ src/
β βββ types.ts
β βββ user.repository.ts
β βββ user.service.ts
β βββ user.service.spec.ts
βββ tsconfig.json
βββ jest.config.js
βββ package.json
package.json
{
"name": "suites-nestjs-jest-example",
"private": true,
"scripts": {
"test": "jest"
},
"dependencies": {
"@nestjs/common": "^10.3.10",
"@nestjs/core": "^10.3.10",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@suites/unit": "^3.0.0-alpha.2",
"@suites/di.nestjs": "^3.0.0-alpha.2",
"@suites/doubles.jest": "^3.0.0-alpha.2",
"jest": "^29.7.0",
"ts-jest": "^29.1.5",
"typescript": "^5.5.3"
}
}
Create a basic tsconfig.json
:
tsconfig.json
{
"compilerOptions": {
"target": "es2020",
"types": ["node", "jest"],
"module": "commonjs",
"noEmit": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node"
}
}
Configure Jest for TypeScript:
module.exports = {
testEnvironment: 'node',
testRegex: '.spec.ts$',
transform: { '.ts': ['ts-jest', { isolatedModules: true } ] },
};
Create the Classesβ
Let's create a simple application with a UserService
that depends on a UserRepository
to fetch user data.
Define the Interfaces and Classesβ
Here are the interfaces and classes we'll use in our example:
export interface User {
id: number;
name: string;
email: string;
}
import { Injectable } from 'any-di-framework';
@Injectable()
export class UserRepository {
async getUserById(id: number): Promise<User> {
// Imagine this fetches from a database
return { id, name: 'John Doe', email: 'john@doe.com' };
}
}
import { Injectable } from 'any-di-framework';
import { UserRepository } from './user.repository';
import { User } from './types';
@Injectable()
export class UserService {
constructor(private userRepository: UserRepository) {}
async getUserName(id: number): Promise<string> {
const user = await this.userRepository.getUserById(id);
return user.name;
}
}
Set Up the Testβ
To test the UserService
class in isolation, we'll use the TestBed
factory from the @suites/unit
package to create
our test environment.
Example Setup and Testβ
Hereβs the setup and test for UserService
:
import { TestBed } from '@suites/unit';
import type { Mocked } from '@suites/unit';
import { UserService } from './user.service';
import { UserRepository } from './user.repository';
describe('User Service Unit Spec', () => {
let userService: UserService;
let userRepository: Mocked<UserRepository>;
beforeAll(async () => {
const { unit, unitRef } = await TestBed.solitary(UserService).compile();
userService = unit;
userRepository = unitRef.get(UserRepository);
});
it('should return the user name and call repository', async () => {
userRepository.getUserById.mockResolvedValue({id: 1, name: 'John Doe'});
const result = await userService.getUserName(1);
expect(userRepository.getUserById).toHaveBeenCalledWith(1);
expect(result).toBe('John Doe');
});
});
When the class under test is instantiated using TestBed.solitary()
, all its dependencies are automatically mocked.
These mocks start as stubs with no predefined behaviors or return values.
This setup offers a clean slate, allowing to define specific behaviors required for each test scenario.
Running the Testβ
Run your tests with:
$ npm test
Reviewβ
Explanation of unit
and unitRef
β
- unit: This represents the instance of the class under test created by the
TestBed
. - unitRef: This allows you to retrieve instances of the mocked dependencies created by the
TestBed
.
The Mocked<T>
Typeβ
This type is used to type the mocked instances of the classes. This type is provided by the @suites/unit
package. This type relies on the mocking library used in the test environment.
Key Highlightsβ
-
Solitary Unit Testing: We're using
TestBed.solitary()
to isolate theUserService
class. -
Automatic Mocking: When the class under test (
UserService
) is instantiated usingTestBed.solitary()
, all its dependencies are automatically mocked. -
Virtual DI Container: Suites skips the full DI container and creates an isolated container leveraging the DI framework's reflection and metadata.
Why there isn't a DI container here?β
Suites bypasses the traditional DI container by directly utilizing the DI framework's reflection and metadata capabilities to construct an isolated test environment. This means that instead of loading the entire DI container, Suites creates a lightweight, virtual container that mirrors the dependency injection mechanism. This streamlined approach reduces setup complexity and overhead, leading to faster test execution while still benefiting from dependency injection principles.
More than Solitaryβ
In this quick start, we've focused on a solitary test setup. To explore more complex scenarios, including sociable unit tests, check out the Unit Testing section.
Adapting to Different Librariesβ
Although this example uses Jest and NestJS annotations, Suites supports various DI frameworks and mocking libraries. To adapt this guide to your preferred tools, simply install the appropriate Suites adapters and follow the same principles:
- DI Frameworks:
@suites/di.nestjs
,@suites/di.inversifyjs
,@suites/di.tsyringe
- Mocking Libraries:
@suites/doubles.jest
,@suites/doubles.sinon
,@suites/doubles.vitest
What's Next?β
Now that you've completed the quick start guide, you're ready to explore more advanced testing scenarios with Suites. Check out the Developer Guide section for in-depth tutorials and examples.
If you have any questions or need help, feel free to reach out to the Suites community on Github Discussions.