Testing
Table of Contents
Introduction
NodeArch provides a robust framework for writing and running test cases. Using decorators like @Test
, @BeforeEach
, and @Case
, you can structure your tests in a clear and maintainable manner.
Setup
Before writing your test cases, ensure you have NodeArch and the necessary dependencies installed.
npm install @nodearch/mocha chai
Import the required modules in your test files:
import { BeforeEach, Case, Test } from '@nodearch/mocha';
import { expect } from 'chai';
Basic Test Case Structure
A typical test case in NodeArch involves creating a test class and using decorators to define test methods.
@Test()
export class MyTest {
constructor(private myService: MyService) {}
@BeforeEach()
setup() {
// Setup code here
}
@Case()
async myTestCase() {
// Test case code here
expect(true).to.be.true;
}
}
Decorators
@Test
The @Test decorator marks a class as a test suite. This class can contain multiple test cases.
@Test()
export class MyTest { ... }
@BeforeEach
The @BeforeEach decorator marks a method to be executed before each test case in the class. It is typically used to set up preconditions.
@BeforeEach()
setup() {
// Code to run before each test
}
@Case
The @Case
decorator marks a method as a test case. It can be used in several ways to customize the test case, including setting a title and providing parameters.
@Case()
async myTestCase() {
// Test case code
}
Usage
The @Case
decorator can be used in the following ways:
Without Parameters: Marks a method as a test case with the method name as the title.
@Case()
async myTestCase() {
// Test case code
}With Title: Marks a method as a test case with a custom title.
@Case('Custom Test Case Title')
async myTestCase() {
// Test case code
}With Options: Marks a method as a test case with options including title, active state, and parameters.
@Case({ title: 'Custom Test Case Title', active: true, params: { id: 1 } })
async myTestCase() {
// Test case code
}With Title and Options: Marks a method as a test case with a custom title and additional options.
@Case('Custom Test Case Title', { active: true, params: { id: 1 } })
async myTestCase() {
// Test case code
}
Arguments
- title: string (optional) - The title of the test case.
- options: ITestCaseOptions (optional) - Additional options for the test case.
export interface ITestCaseOptions {
title?: string;
active?: boolean;
params?: object;
}- title: string (optional) - The title of the test case.
- active: boolean (optional) - Indicates whether the test case is active. Defaults to true.
- params: object (optional) - Parameters to pass to the test case.
@Mock
The @Mock decorator is used to mock a class or a service. This is useful for isolating the unit under test.
@Mock(SampleComponent)
export class SampleMock { ... }
@Override
The @Override decorator is used to replace the real implementation of a dependency with a mock implementation in the test class.
@Override(SampleMock)
export class SampleTest { ... }
Writing Test Cases
Example: User Service Tests
Below is an example of writing test cases for a UserService class. This example includes tests for fetching users, adding a user, and getting a user by ID.
import { BeforeEach, Case, Test } from '@nodearch/mocha';
import { UserService } from './user.service.js';
import { expect } from 'chai';
import { IUser } from './user.interface.js';
import { UserRepository } from './user.repository.js';
@Test()
export class UserTest {
constructor(
private userService: UserService,
private userRepository: UserRepository
) {}
@BeforeEach()
cleanupData() {
this.userRepository.removeAll();
this.userRepository.addUser({
id: 1,
name: 'John Doe',
email: 'john.d@email.com',
age: 20,
role: 'admin',
language: 'en'
});
}
@Case()
async getUsers() {
const users = await this.userService.getUsers();
expect(users).length(1);
expect(users[0].name).to.be.equal('John Doe');
}
@Case()
async addUser() {
const data: Omit<IUser, "id"> = {
name: 'Jane Doe',
email: 'jane.d@email.com',
age: 20,
role: 'admin',
language: 'fr'
};
await this.userService.addUser(data);
expect(await this.userRepository.getUsersCount()).to.be.equal(2);
}
@Case('Get User by existing Id', {params: { id: 1 }})
@Case('Get User by non-existing Id', {params: { id: 2 }})
async getUserById({ id }: { id: number }) {
const user = await this.userService.getUserById(id);
if (id === 1) {
expect(user.name).to.be.equal('John Doe');
} else if (id === 2) {
expect(user).to.be.undefined;
}
}
}
Mocking Dependencies
In some cases, you might need to mock dependencies to isolate the unit being tested. NodeArch allows you to override dependencies using the @Override decorator.
Example: Mocking User Repository
import { Case, Override, Test } from '@nodearch/mocha';
import { UserService } from './user.service.js';
import { expect } from 'chai';
import { UserRepoMock } from './user-repo.mock.js';
@Test()
@Override(UserRepoMock)
export class UserTest {
constructor(
private userService: UserService,
) {}
@Case()
async getUsers() {
const users = await this.userService.getUsers();
expect(users).length(1);
expect(users[0].name).to.be.equal('Mocked User'); // The value is from UserRepoMock
}
}
user-repo-mock
Below is the implementation of the mock user repository:
import { Mock } from '@nodearch/mocha';
import { UserRepository } from './user.repository.js';
@Mock(UserRepository)
export class UserRepoMock {
async getUsers() {
return [
{
id: 1000,
name: 'Mocked User',
email: 'mocked.user@email.com',
age: 1,
role: 'admin',
language: 'en'
},
];
}
}