- Configuring test framework
- Using
configureControllerTest
- Using
configureServiceTest
- Bootstrap whole server
fastify-decorators provide dependency injection functionality only when reflect-metadata
required.
It leads to mandatory of requiring this package to tests as well.
Packages to be installed:
Also if you're using imports with file extensions then you will need:
Example configuration: jest.environment.js:
const NodeEnvironment = require('jest-environment-node');
class FastifyDecoratorsTestEnvironment extends NodeEnvironment {
setup() {
require('reflect-metadata');
this.global.Reflect = Reflect;
return super.setup();
}
}
module.exports = FastifyDecoratorsTestEnvironment;
jest.config.js:
module.exports = {
preset: 'ts-jest',
// Note resolver required only when using imports with extensions
resolver: 'jest-ts-webcompat-resolver',
// In test environment we setup reflect-metadata
testEnvironment: './jest.environment.cjs',
// Jest does not support ESM modules well, so you will need to define mappings to CJS modules
moduleNameMapper: {
'^fastify-decorators/testing$': 'fastify-decorators/testing/index.cjs',
'^fastify-decorators$': 'fastify-decorators/index.cjs',
},
};
Packages to be installed:
Example configuration:
.mocharc.yml:
# Common Mocha options
bail: false
timeout: 10000
enable-source-maps: true
v8-stack-trace-limit: 100
extension:
- 'ts'
# Enable experimental TS ESM loader
loader:
- ts-node/esm
# Specify root hooks file, required for `reflect-metadata` loading
require:
- test/mocha-hooks.ts
# Specify tests pattern
spec:
- test/**/*.test.ts
- test/**/*.spec.ts
test/mocha-hooks.ts:
import 'reflect-metadata';
The configureControllerTest(options)
function registers a Controller and allow you to mock out the Services for testing functionality.
You can write tests validating behaviors corresponding to the specific result of Controller interacting with mocked services.
Note: if mock was not provided for one or more dependencies then originals will be used.
Usage:
import { FastifyInstance } from 'fastify';
import { configureControllerTest } from 'fastify-decorators/testing';
import { AuthController } from '../src/auth.controller';
import { AuthService } from '../src/auth.service';
describe('Controller: AuthController', () => {
let instance: FastifyInstance;
const authService = { authorize: jest.fn() };
beforeEach(async () => {
instance = await configureControllerTest({
controller: AuthController,
mocks: [
{
provide: AuthService,
useValue: authService,
},
],
});
});
afterEach(() => jest.restoreAllMocks());
it(`should reply with 'ok' if authorization success`, async () => {
authService.authorize.and.returnValue(Promise.resolve(true));
const result = await instance.inject({
url: '/authorize',
method: 'POST',
payload: { login: 'test', password: 'test' },
});
expect(result.json()).toEqual({ message: 'ok' });
});
});
The configureControllerTest
decorate Fastify instance with controller
property which may be used to access controller instance.
Note: controller will be undefined
in case "per request" type is used.
Example:
import { FastifyInstance } from 'fastify';
import { configureControllerTest, FastifyInstanceWithController } from 'fastify-decorators/testing';
import { AuthController } from '../src/auth.controller';
describe('Controller: AuthController', () => {
let instance: FastifyInstanceWithController<AuthController>;
beforeEach(async () => {
instance = await configureControllerTest({
controller: AuthController,
});
});
afterEach(() => jest.restoreAllMocks());
it(`should reply with 'ok' if authorization success`, async () => {
const controllerInstance = instance.controller;
jest.spyOn(controllerInstance, 'authorize').mockReturnValue(Promise.resolve({ message: 'ok' }));
const result = await instance.inject({
url: '/authorize',
method: 'POST',
payload: { login: 'test', password: 'test' },
});
expect(result.json()).toEqual({ message: 'ok' });
});
});
The configureControllerTest(options)
is pretty close to configureControllerTest
the difference is that this method returns service with mocked dependencies.
Note: if mock was not provided for one or more dependencies then originals will be used.
For those services which has no method with @Initializer
decorator, then configureServiceTest
will return an instance of it.
Usage:
import { configureServiceTest } from 'fastify-decorators/testing';
import { RolesService } from '../src/roles.service';
import { AuthService } from '../src/auth.service';
describe('Service: AuthService', () => {
let service: AuthService;
const rolesService = { isTechnical: jest.fn(), isAdmin: jest.fn() };
beforeEach(() => {
service = configureServiceTest({
service: AuthService,
mocks: [
{
provide: RolesService,
useValue: rolesService,
},
],
});
});
afterEach(() => jest.restoreAllMocks());
it(`should reply with 'ok' if authorization success`, async () => {
rolesService.isTechnical.and.returnValue(true);
rolesService.isAdmin.and.returnValue(false);
const bearer = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6W119.0Dd6yUeJ4UbCr8WyXOiK3BhqVVwJFk5c53ipJBWenmc';
const result = service.hasSufficientRole(bearer);
expect(result).toBe(true);
});
});
If service has method with @Initializer
decorator, then configureServiceTest
will return intersection of an instance and Promise.
You can work with service like it has no @Initializer
unless you await it.
import { configureServiceTest } from 'fastify-decorators/testing';
import { RolesService } from '../src/roles.service';
import { AuthService } from '../src/auth.service';
describe('Service: AuthService', () => {
let service: AuthService;
const rolesService = { isTechnical: jest.fn(), isAdmin: jest.fn() };
beforeEach(async () => {
service = await configureServiceTest({
service: AuthService,
mocks: [
{
provide: RolesService,
useValue: rolesService,
},
],
});
});
afterEach(() => jest.restoreAllMocks());
it(`should reply with 'ok' if authorization success`, async () => {
rolesService.isTechnical.and.returnValue(true);
rolesService.isAdmin.and.returnValue(false);
const bearer = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6W119.0Dd6yUeJ4UbCr8WyXOiK3BhqVVwJFk5c53ipJBWenmc';
const result = service.hasSufficientRole(bearer);
expect(result).toBe(true);
});
});
It also possible to test application by bootstrap everything. It comparable to normal run. Difference is server will not run.
Note: read more at Fastify testing documentation
Example:
src/index.ts:
import fastify from 'fastify';
const instance = fastify({
/* options */
});
/* your code */
export { instance };
test/auth.spec.ts:
import { instance } from '../src';
describe('Application: authorization', () => {
beforeEach(() => {
/* Setup logic for test */
});
afterEach(() => {
/* Teardown logic for test */
});
it('should check credentials and reply with result', async () => {
const payload = { login: 'test', password: 'test' };
const result = await instance.inject({
url: '/auth/authorize',
method: 'POST',
payload,
});
expect(JSON.parse(result.body)).toEqual({ message: 'ok' });
});
});