Skip to content

feat:add e2e testing initial setup #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@
},
"devDependencies": {
"@compodoc/compodoc": "^1.1.23",
"@faker-js/faker": "^9.9.0",
"@nestjs/cli": "^11.0.2",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.9",
"@testcontainers/postgresql": "^11.5.1",
"@types/bcrypt": "^5.0.2",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
Expand All @@ -89,6 +91,7 @@
"kysely-ctl": "^0.11.1",
"prettier": "^3.2.5",
"supertest": "^6.3.4",
"testcontainers": "^11.5.1",
"ts-jest": "^29.1.2",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
Expand Down
607 changes: 604 additions & 3 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions test/@types/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { StartedPostgreSqlContainer } from '@testcontainers/postgresql';

declare global {
// eslint-disable-next-line no-var
var __TEST__: boolean;
// eslint-disable-next-line no-var
var __Container__: {
postgres: StartedPostgreSqlContainer | null;
};
}

export {};
17 changes: 17 additions & 0 deletions test/app.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { TestingModule } from '@nestjs/testing';
import { TestModuleFactory } from './factory/test-module.factory';
import { INestApplication } from '@nestjs/common';

describe('AppController (e2e)', () => {
let testingModule: TestingModule;
let app: INestApplication;

beforeAll(async () => {
testingModule = await TestModuleFactory.createTestModule();
app = testingModule.createNestApplication();
await app.init();
}, 60000);
it('should return "Hello World!"', () => {
expect(true).toBe(true);
});
});
41 changes: 41 additions & 0 deletions test/factory/test-module.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { createTestConfig } from '../helpers/test-configuration';
import { TestAppModule } from '../test-app.module';
import { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
export class TestModuleFactory {
static async createTestModule(): Promise<TestingModule> {
const postgresContainer = globalThis.__Container__
.postgres as StartedPostgreSqlContainer;

// this is to ignore warning from env not found error. does not matter what we put
process.env.DATABASE_URL = postgresContainer.getConnectionUri();
process.env.SECRET = 'ASD';
const testConfig = createTestConfig(
postgresContainer.getConnectionUri() + '?sslmode=disable',
);
const moduleRef = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
load: [() => testConfig],
isGlobal: true,
}),
TestAppModule,
],
})
.overrideProvider(ConfigService)
.useValue({
get: (key: string) => {
const keys = key.split('.');
let value = testConfig;
for (const k of keys) {
value = value[k];
}
return value;
},
})
.compile();

return moduleRef;
}
}
17 changes: 17 additions & 0 deletions test/helpers/test-configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// test/config/test-configuration.ts
import { AppConfig, LoggerFormat } from '../../src/config/configuration';

export const createTestConfig = (databaseUrl: string): AppConfig => ({
corsMaxAge: 86400,
database: {
poolSize: 5,
url: databaseUrl,
},
port: 3000,
secret: 'kugk2iz30q5mlc6056der8sdnadibb',
logger: {
format: LoggerFormat.Json,
level: 'error',
},
isDevEnv: false,
});
102 changes: 102 additions & 0 deletions test/helpers/test-container.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
PostgreSqlContainer,
StartedPostgreSqlContainer,
} from '@testcontainers/postgresql';
import { DB } from 'database/schema/db';
import {
CamelCasePlugin,
FileMigrationProvider,
Kysely,
Migrator,
PostgresDialect,
} from 'kysely';
import { join } from 'path';
import { promises as fs } from 'fs';
import * as path from 'path';

import { Pool } from 'pg';

export class TestContainerHelper {
private static instance: TestContainerHelper;
private static container: StartedPostgreSqlContainer;

private constructor() {}

public static getInstance(): TestContainerHelper {
if (!TestContainerHelper.instance) {
TestContainerHelper.instance = new TestContainerHelper();
}
return TestContainerHelper.instance;
}

public async startPostgresContainer(): Promise<StartedPostgreSqlContainer> {
if (!TestContainerHelper.container) {
TestContainerHelper.container = await new PostgreSqlContainer(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can have multiple test containers for the project, so it would be a better approach to name this as pgInstance

'postgres:15-alpine',
).start();
console.log('PostgreSQL container started !!!');

const db = new Kysely<DB>({
dialect: new PostgresDialect({
pool: new Pool({
connectionString: `${TestContainerHelper.container.getConnectionUri()}?sslmode=disable`,
max: 5,
}),
}),
plugins: [new CamelCasePlugin()],
});
await this.runMigrations(db);
await db.destroy();
}
return TestContainerHelper.container;
}

public async stopPostgresContainer(): Promise<void> {
if (TestContainerHelper.container) {
await TestContainerHelper.container.stop();
TestContainerHelper.container = undefined;
}
}

private async runMigrations(db: Kysely<DB>): Promise<void> {
console.log('Running migrations...');

const migrationsPath = join(__dirname, '../../src/database/migrations');

try {
await fs.access(migrationsPath);

const migrator = new Migrator({
db: db,
provider: new FileMigrationProvider({
fs,
path,
migrationFolder: migrationsPath,
}),
allowUnorderedMigrations: true,
});

const { error, results } = await migrator.migrateToLatest();

results?.forEach((it) => {
if (it.status === 'Success') {
console.log(
`Migration "${it.migrationName}" was executed successfully`,
);
} else if (it.status === 'Error') {
console.error(`Failed to execute migration "${it.migrationName}"`);
}
});

if (error) {
console.error('Failed to migrate:', error);
throw error;
}

console.log('Migrations completed successfully');
} catch (error) {
console.error('Migration error:', error);
throw error;
}
}
}
12 changes: 10 additions & 2 deletions test/jest-e2e.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"rootDir": "./",
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/src/$1"
},
"modulePaths": ["<rootDir>"],
"moduleDirectories": ["<rootDir>/", "node_modules", "src", "<rootDir>/../"],
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
},
"globalSetup": "./setup/global-setup.ts",
"globalTeardown": "./setup/global-teardown.ts",
"setupFilesAfterEnv": ["<rootDir>/@types/globals.d.ts"]
}
12 changes: 12 additions & 0 deletions test/mocks/user-mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const MOCK_USERS = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test/users/mock/users.ts

user1: {
email: 'user1@example.com',
password: 'password1',
},
user2: {
email: 'user2@example.com',
password: 'password2',
},
};

export default MOCK_USERS;
Loading