Skip to main content

How to add the Integration Test Suite to a Project

1. Add the Integration Test Tag to the Project

In order to run integration tests on a nest application, the project needs to be flagged as an integration-test project. This is done by adding the integration-test tag to the project in the project.json file. This will trigger the build pipelines to test the project on a integration test runner.

{
"name": "my-project",
"tags": ["integration-test"]
}

2. Configuring Jest

You will need to create a few files to set up the test suite. We're going to hook into Jest's global setup and teardown hooks to create a test schema in the database before running the tests and then destroy it after the tests have run.

First, we need to create our global setup script:

./test-suite/GlobalSetup.ts
/* eslint-disable ban/ban */
require('tsconfig-paths/register');

import type { Config } from 'jest';
import { DataSource } from 'typeorm';
import type { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
import dataSource from '../src/infrastructure/TypeOrm/DataSource';

module.exports = async function (globalConfig: Config, projectConfig: Config): Promise<void> {
const schema = '<app-name>_test'; // Replace <app-name> with the name of your app
const testDataSource = new DataSource({
...dataSource.options,
schema,
} as PostgresConnectionOptions);
try {
await testDataSource.initialize();
const runner = testDataSource.createQueryRunner();
await runner.createSchema(schema, true);
await testDataSource.synchronize();

await runner.query(
`CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS btree_gist; CREATE EXTENSION IF NOT exists btree_gin; CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`,
);

await testDataSource.destroy();
} catch (error) {
console.error('Failed to setup global test schema');
console.error(error);
process.exit(1);
}
};

Then we tell nest to load this file by adding the following to the jest.config.ts file:

./jest.config.ts
/* eslint-disable */
module.exports = {
// ...
globalSetup: '<rootDir>/test-suite/GlobalSetup.ts',
};

Next, update the typescript configurations to include the test suite files only in the test environment

./tsconfig.spec.json
{
//...
"include": [
// ...
"test-suite/**/*.ts"
]
}
./tsconfig.json
{
//...
"exclude": [
// ...
"test-suite/**/*.ts"
]
}

3. Create a Test Suite EntityHelper Service

When we interact with the test suite, we want an easy way to create test entities. We can create a helper service to do this for us. This service will be used to create entities in the test suite. Note how each method returns a CommandSetup object, which will will use later to run the tests.

./test-suite/EntityHelper.ts
import { GameType, ItemQuality, Locale } from '@tsm/types/warcraft';
import { AddItemModifier } from '../src/application/Command/AddItemModifier';
import type { Item } from '../src/domain/Entity/Item';
import type { ItemModifierRestResource } from '../src/infrastructure/RestResource/ItemModifierRestResource';

interface CommandSetup<C, E> {
command: C;
expected: E;
}

export class EntityHelper {
public createItemModifier(
gameType: GameType,
warcraftId: number,
modifier: string,
): CommandSetup<AddItemModifier, ItemModifierRestResource> {
return {
command: new AddItemModifier(gameType, warcraftId, modifier),
expected: {
id: warcraftId,
modifier,
},
};
}
}

4. Write the Tests

Now we can write the tests. We can use the EntityHelper service to create entities in the test suite. Here is an example of a test suite:

./src/ItemModifier.spec.ts
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
import { Test, TestingModule } from '@nestjs/testing';
import type { Client, TokenSet } from 'openid-client';
import { FrameworkModule, configureForTesting } from '@tsm/nest/framework';
import { createApplication, type SetupFunction, type TestFunction } from '@tsm/nest/jest-testing-integration';
import { GameType } from '@tsm/types/warcraft';
import { ItemModifierController } from './ItemModifierController';
import { EntityHelper } from '../../../test-suite/Helpers';
import type { AppConfig } from '../../AppConfig';
import { AddItemModifier } from '../../application/Command/AddItemModifier';
import { AppModule } from '../../AppModule';

describe('ItemModifierController', () => {
let controller: ItemModifierController;
let entityHelper: EntityHelper;
let tokenSet: TokenSet;

const config = configureForTesting<AppConfig>({
// inject any application configuration here
});

let testSuite: ApiTestSuite;
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [FrameworkModule.forRoot(config, [AppModule.forRoot(config), AppModule.forApi(config)])],
}).compile();

testSuite = await ApiTestSuite.create(module);

itemApiHttpClient = module.get(ItemApiHttpClient);
});
afterEach(async () => {
await testSuite.shutdown();
});
beforeEach(async () => {
await testSuite.reset();
});

// Before each test, extract any services you will need, or do any further setup
beforeEach(async () => {
controller = app.get(ItemModifierController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});

describe('#get', () => {
it('should create a resource', async () => {
const warcraftId = 1;
const gameType = GameType.RETAIL;

// Create a test entity. Here we use the EntityHelper service to create a test entity.
const entity = entityHelper.createBasicItem(gameType, warcraftId);
await testSuite.applyState(entity.command);

// Run the test
const response = await testSuite.test({
method: 'POST',
url: `/${gameType}/item-modifiers/${warcraftId}`,
payload: {
modifier: `i:${warcraftId}:2:2:3:4`,
},
});

// Assert the response
expect(response.statusCode).toEqual(201);
expect(response.json()).toEqual(entity.expected);
});
});
});