testing-vitest

Use this skill when writing tests (*.spec.ts , *.e2e-spec.ts ) for NestJS + MikroORM projects.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "testing-vitest" with this command: npx skills add bloominggrace/create-fullstack-service/bloominggrace-create-fullstack-service-testing-vitest

Vitest Testing Guide

Use this skill when writing tests (*.spec.ts , *.e2e-spec.ts ) for NestJS + MikroORM projects.

Test Types

  • Integration Tests (*.spec.ts): Service layer, uses real DB

  • E2E Tests (*.e2e-spec.ts): Full API endpoint flow

⚠️ Do NOT write controller unit tests ⚠️ Use real DB connection instead of mocks

Service Integration Test Structure

import { faker } from '@faker-js/faker'; import { type EntityManager, MikroORM } from '@mikro-orm/core'; import { MikroOrmModule } from '@mikro-orm/nestjs'; import { ConflictException, NotFoundException } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; import { plainToInstance } from 'class-transformer'; import { validateOrReject } from 'class-validator';

import { CreateEntityDto } from './dto/create-entity.dto'; import { Entity } from './entities/entity.entity'; import { EntityService } from './entity.service';

async function generateCreateEntityDto(): Promise<CreateEntityDto> { const dto = plainToInstance(CreateEntityDto, { field: faker.lorem.word(), }); await validateOrReject(dto); return dto; }

describe('EntityService 테스트', () => { let testingModule: TestingModule; let mikroOrm: MikroORM; let entityService: EntityService; let entityManager: EntityManager;

beforeAll(async () => { testingModule = await Test.createTestingModule({ imports: [MikroOrmModule.forRoot(), MikroOrmModule.forFeature([Entity])], providers: [EntityService], }).compile();

mikroOrm = testingModule.get(MikroORM);
entityService = testingModule.get(EntityService);

});

beforeEach(async () => { entityManager = mikroOrm.em.fork(); Object.assign(entityService, { entityManager }); await entityManager.begin(); });

afterEach(async () => { await entityManager.rollback(); });

afterAll(async () => { await testingModule.close(); });

describe('생성', () => { test('신규 엔티티를 생성한다.', async () => { const dto = await generateCreateEntityDto(); const entity = await entityService.create(dto);

  expect(entity.id).toBeDefined();
});

}); });

Transaction Rollback Pattern

Each test must run independently:

beforeEach(async () => { entityManager = mikroOrm.em.fork(); Object.assign(entityService, { entityManager }); await entityManager.begin(); // Start transaction });

afterEach(async () => { await entityManager.rollback(); // Rollback to clean data });

E2E Test Structure

import { faker } from '@faker-js/faker'; import { type INestApplication, ValidationPipe } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { Logger } from 'nestjs-pino'; import request from 'supertest'; import { type App } from 'supertest/types';

import { AppModule } from '../src/app.module';

describe('E2E 테스트', () => { let app: INestApplication<App>;

beforeAll(async () => { const moduleFixture = await Test.createTestingModule({ imports: [AppModule], }).compile();

app = moduleFixture.createNestApplication();
app.useLogger(app.get(Logger));
app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }),
);
await app.init();

});

afterAll(async () => { await app.close(); });

describe('EntityController 테스트', () => { describe('(GET) /entities/:id', () => { test('엔티티를 조회한다.', async () => { const entityId = '019afbdc-b30f-771f-a859-7c17a7dea978'; const response = await request(app.getHttpServer()) .get(/entities/${entityId}) .expect(200);

    expect(response.body.id).toEqual(entityId);
  });

  test('존재하지 않는 엔티티를 조회한다.', async () => {
    await request(app.getHttpServer())
      .get(`/entities/${faker.string.uuid()}`)
      .expect(404);
  });
});

}); });

Test Naming Conventions

describe Blocks

  • Top level: {Service/Controller}Service 테스트

  • Feature level: Korean description of behavior (e.g., 사용자 생성 , OTP 발송 )

  • HTTP method: (METHOD) /path/to/endpoint

test Blocks

  • Write in Korean with specific scenario description

  • End with ~한다. format

describe('사용자 생성', () => { test('신규 사용자를 생성한다.', async () => { }); test('중복 사용자를 생성한다.', async () => { }); });

DTO Generation Helper Functions

Generate test DTOs using helper functions:

async function generateCreateUserDto(): Promise<CreateUserDto> { const dto = plainToInstance(CreateUserDto, { phoneNumber: faker.helpers.arrayElement([ +82101${faker.string.numeric(7)}, +81901${faker.string.numeric(7)}, ]), }); await validateOrReject(dto); return dto; }

Test Fixtures

Separate reusable test data into fixture files:

// test/phone-number.fixture.ts import { faker } from '@faker-js/faker';

export const PHONE_NUMBER_TEST_CASES = [ { country: '한국', phoneNumber: () => +82101${faker.string.numeric(7)} }, { country: '일본', phoneNumber: () => +81901${faker.string.numeric(7)} }, { country: '중국', phoneNumber: () => +86131${faker.string.numeric(8)} }, ];

export function generatePhoneNumber(): string { return faker.helpers.arrayElement(PHONE_NUMBER_TEST_CASES).phoneNumber(); }

test.each Pattern

Repeat tests for multiple cases:

test.each(PHONE_NUMBER_TEST_CASES)( '$country 전화번호로 OTP를 발송한다.', async ({ phoneNumber }) => { const result = await service.sendOtp({ phoneNumber: phoneNumber() }); expect(result.message).toBe('OTP 발송 성공'); } );

File Structure

src/ ├── {module}/ │ └── {module}.service.spec.ts # Service test test/ └── app.e2e-spec.ts # E2E tests

Vitest Configuration (vitest.config.ts)

import swc from 'unplugin-swc'; import { defineConfig } from 'vitest/config';

export default defineConfig({ test: { globals: true, globalSetup: 'vitest.globalSetup.ts', include: ['src//*.spec.ts', 'test//*.e2e-spec.ts'], }, plugins: [swc.vite()], });

Global Setup (vitest.globalSetup.ts)

import { MikroORM } from '@mikro-orm/core';

import { TestSeeder } from './mikro-orm.seeders';

export async function setup(): Promise<void> { const mikroOrm = await MikroORM.init(); await mikroOrm.schema.ensureDatabase(); await mikroOrm.schema.updateSchema(); await mikroOrm.schema.clearDatabase(); await mikroOrm.seeder.seed(TestSeeder); await mikroOrm.close(); }

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

mikro-orm-entity

No summary provided by upstream source.

Repository SourceNeeds Review
General

dto-response

No summary provided by upstream source.

Repository SourceNeeds Review
General

pr-title

No summary provided by upstream source.

Repository SourceNeeds Review