설치
$ npm i --save-dev @nestjs/testing
Unit Testing.
Testing files should have a .spec
or .test
suffix.
isolated testing
- 프레임워크로부터 독립적
- dependency injection 가 없다
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
describe('CatsController', () => {
let catsController: CatsController;
let catsService: CatsService;
beforeEach(() => {
catsService = new CatsService();
catsController = new CatsController(catsService);
});
describe('findAll', () => {
it('should return an array of cats', async () => {
const result = ['test'];
jest.spyOn(catsService, 'findAll').mockImplementation(() => result);
expect(await catsController.findAll()).toBe(result);
});
});
});
Testing Utilities
@nestjs/testing
: 강력한 테스트 프로세스를 가능하게 함
import { Test } from '@nestjs/testing'; //
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
describe('CatsController', () => {
let catsController: CatsController;
let catsService: CatsService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [CatsController],
providers: [CatsService],
}).compile();
catsService = moduleRef.get<CatsService>(CatsService);
catsController = moduleRef.get<CatsController>(CatsController);
});
describe('findAll', () => {
it('should return an array of cats', async () => {
const result = ['test'];
jest.spyOn(catsService, 'findAll').mockImplementation(() => result);
expect(await catsController.findAll()).toBe(result);
});
});
});
Test
- 전체 Nest 런타임을 moke하는 애플리케이션 실행 컨텍스트를 제공하는 데 유용합니다.
- moke 및 overriding을 포함하여 클래스 인스턴스를 쉽게 관리할 수 있는 후크를 제공합니다.
createTestingModule()
:- 모듈 metadata object를 인수로 사용합니다. (@Module() 데코레이터에 전달한 것과 동일한 object).
- TestingModule 인스턴스를 반환합니다.
compile()
:- 이 방법은 종속성이 있는 모듈을 bootstrap 합니다. (NestFactory.create()를 사용하여 기존 main.ts 파일에서 애플리케이션을 부트스트랩하는 방식과 유사).
- 테스트 준비가 된 모듈을 반환합니다.
- asynchronous ⇒ await 반환
- 모듈이 컴파일되면 get() 메서드를 사용하여 선언된 정적 인스턴스(컨트롤러 및 공급자)를 검색할 수 있습니다.
Auto moking
- 많은 의존성이 있을 때 유용
- 이 기능을 사용하려면 createTestingModule()을 useMocker() 메서드와 연결하여 종속성 모의 객체에 대한 팩토리를 전달해야 합니다.
- 이 팩토리는 인스턴스 토큰인 선택적 토큰, Nest 공급자에게 유효한 모든 토큰을 가져와 모의 구현을 반환할 수 있습니다. 아래는 jest-mock을 사용하여 일반 모커를 생성하고 jest.fn()을 사용하여 CatsService에 대한 특정 모커를 생성하는 예입니다.
import { ModuleMocker, MockFunctionMetadata } from 'jest-mock';
const moduleMocker = new ModuleMocker(global);
describe('CatsController', () => {
let controller: CatsController;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [CatsController],
})
.useMocker((token) => {
const results = ['test1', 'test2'];
if (token === CatsService) {
return { findAll: jest.fn().mockResolvedValue(results) };
}
if (typeof token === 'function') {
const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata<any, any>;
const Mock = moduleMocker.generateFromMetadata(mockMetadata);
return new Mock();
}
})
.compile();
controller = moduleRef.get(CatsController);
});
});
End-to-end testing
- 보다 종합적인 수준에서 클래스와 모듈의 상호 작용 Test
- end User ←→ production
import * as request from 'supertest';
import { Test } from '@nestjs/testing';
import { CatsModule } from '../../src/cats/cats.module';
import { CatsService } from '../../src/cats/cats.service';
import { INestApplication } from '@nestjs/common';
describe('Cats', () => {
let app: INestApplication;
let catsService = { findAll: () => ['test'] };
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [CatsModule],
})
.overrideProvider(CatsService)
.useValue(catsService)
.compile();
app = moduleRef.createNestApplication();
await app.init();
});
it(`/GET cats`, () => {
return request(app.getHttpServer())
.get('/cats')
.expect(200)
.expect({
data: catsService.findAll(),
});
});
afterAll(async () => {
await app.close();
});
});
NestJs TypeORM 테스트 코드
Fixtures
- Fixture란
- Test Fixture : 고정되어 있는 물체 를 의미
- 테스트 실행을 위해 베이스라인으로서 사용되는 객체들의 고정된 상태
- 테스트를 위한 기본으로 세팅된 데이터 구성
beforeEach(() => {
setFixtures(...);
});
테스트 코드를 실행할 때 beforeEach
를 통해 테스트 코드가 실행되기 전 작업을 명시할 수 있습니다. beforeEach에서 setFixtures 함수를 구성해 줌으로써 테스트케이스가 실행되기 전 정해둔 데이터로 db를 초기화할 수 있습니다. 그럼 모든 테스트는 동일한 데이터를 가지고 테스트 케이스가 실행될 수 있습니다.
그리고 테스트가 끝난 후 afterEach에서 사용한 데이터를 싹 날려줍니다.
afterEach(() => {
truncate all tables..
})
Ex)
// test.spec.ts
setFixtures([[FooRepository, FooEntities]]);
// setFixtures 내부
// beforeEach
await repository.runTransaction(async (tx) => {
await repository.rawQuery('set foreign_key_checks = 0', [], tx);
await repository.insert(JSON.parse(JSON.stringify(data)), tx);
await repository.rawQuery('set foreign_key_checks = 1', [], tx);
});
// afterEach
await repository.runTransaction(async (tx) => {
await repository.rawQuery('set foreign_key_checks = 0', [], tx);
await repository.rawQuery(`truncate ${repository.getTableName()}`, [], tx);
await repository.rawQuery('set foreign_key_checks = 1', [], tx);
});
}
Docker를 이용한 도컬 mysql 설치
- 왜 local에 mysql을 설치하지 않나요?
- 왜 dev서버에 db로 연결하지 않나요?
우선 꼭 docker가 아니어도 됩니다. 로컬에 mysql을 직접 설치해도 됩니다. 다만 테스트코드를 실행하는 자는 꼭 mysql의 설치에 익숙한 백엔드 개발자가 아닐 수 있음에 주의해야 합니다.
프론트엔드 개발자가 테스트코드를 실행할 경우가 있고, 그런경우라면 프론트엔드 개발자도 mysql설치를 해야합니다.
보통 E2E라고 하면 controller <—> db 까지의 테스트라고 생각할 수 있지만, 좀 더 크게 E2E를 본다면 프론트엔드 <—> 백엔드까지의 범위라고 볼 수 있습니다.
그런경우 프론트에서 특정 버턴을 누르고 api 가 호출되어 db에 데이터가 들어가는 것 까지 테스트할 수 있습니다.
(js에서 사용하는 도구로는 cypress와 같은게 있습니다.)
test 할 때 자동으로 docker test database 만들기
server/api-server/testMysql에 dockerfile 생성
FROM mysql:8.2.0
ENV MYSQL_ROOT_PASSWORD=audgml145
ENV MYSQL_DATABASE=gbs
ENV LANG=C.UTF-8
COPY ./docker/mysql/init-test-db.sql /docker-entrypoint-initdb.d/init-test-db.sql
yarn test:docker
: docker 자동 생성 및 test 실행
"test:docker":"cp ./sql/schema.sql ./testMysql/docker/mysql/init-test-db.sql && docker build -t testdb ./testMysql && docker run -it -p 3306:3306 testdb && jest --watch",
cp ./sql/schema.sql ./testMysql/docker/mysql/init-test-db.sq
: 실제 db sql을 test db의 초기 sql로 복사docker build -t testdb ./testMysql
: docker image 생성docker run -it -p 3306:3306 testdb
: 컨테이너 생성 시 -p 옵션을 이용해서 바인딩할 포트를 부여한다.jest --watch
: test 실행
Ref
'nestJS' 카테고리의 다른 글
nestJS 시작하기 (0) | 2023.11.13 |
---|