Nest Test Code - boostcampwm2023/web07-GBS GitHub Wiki
$ npm i --save-dev @nestjs/testing
Testing files should have a .spec
or .test
suffix.
- ํ๋ ์์ํฌ๋ก๋ถํฐ ๋ ๋ฆฝ์
- 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);
});
});
});
@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);
});
});
});
- ์ ์ฒด Nest ๋ฐํ์์ mokeํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์คํ ์ปจํ ์คํธ๋ฅผ ์ ๊ณตํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค.
- moke ๋ฐ overriding์ ํฌํจํ์ฌ ํด๋์ค ์ธ์คํด์ค๋ฅผ ์ฝ๊ฒ ๊ด๋ฆฌํ ์ ์๋ ํํฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
-
createTestingModule()
:- ๋ชจ๋ metadata object๋ฅผ ์ธ์๋ก ์ฌ์ฉํฉ๋๋ค. (@Module() ๋ฐ์ฝ๋ ์ดํฐ์ ์ ๋ฌํ ๊ฒ๊ณผ ๋์ผํ object).
- TestingModule ์ธ์คํด์ค๋ฅผ ๋ฐํํฉ๋๋ค.
-
compile()
:- ์ด ๋ฐฉ๋ฒ์ ์ข ์์ฑ์ด ์๋ ๋ชจ๋์ bootstrap ํฉ๋๋ค. (NestFactory.create()๋ฅผ ์ฌ์ฉํ์ฌ ๊ธฐ์กด main.ts ํ์ผ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ถํธ์คํธ๋ฉํ๋ ๋ฐฉ์๊ณผ ์ ์ฌ).
- ํ ์คํธ ์ค๋น๊ฐ ๋ ๋ชจ๋์ ๋ฐํํฉ๋๋ค.
- asynchronous โ await ๋ฐํ
- ๋ชจ๋์ด ์ปดํ์ผ๋๋ฉด get() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ธ๋ ์ ์ ์ธ์คํด์ค(์ปจํธ๋กค๋ฌ ๋ฐ ๊ณต๊ธ์)๋ฅผ ๊ฒ์ํ ์ ์์ต๋๋ค.
- ๋ง์ ์์กด์ฑ์ด ์์ ๋ ์ ์ฉ
- ์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ค๋ฉด 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);
});
});
- ๋ณด๋ค ์ข ํฉ์ ์ธ ์์ค์์ ํด๋์ค์ ๋ชจ๋์ ์ํธ ์์ฉ 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();
});
});
- 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);
});
}
- ์ local์ mysql์ ์ค์นํ์ง ์๋์?
- ์ dev์๋ฒ์ db๋ก ์ฐ๊ฒฐํ์ง ์๋์?
์ฐ์ ๊ผญ docker๊ฐ ์๋์ด๋ ๋ฉ๋๋ค. ๋ก์ปฌ์ mysql์ ์ง์ ์ค์นํด๋ ๋ฉ๋๋ค. ๋ค๋ง ํ ์คํธ์ฝ๋๋ฅผ ์คํํ๋ ์๋ ๊ผญ mysql์ ์ค์น์ ์ต์ํ ๋ฐฑ์๋ ๊ฐ๋ฐ์๊ฐ ์๋ ์ ์์์ ์ฃผ์ํด์ผ ํฉ๋๋ค.
ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๊ฐ ํ ์คํธ์ฝ๋๋ฅผ ์คํํ ๊ฒฝ์ฐ๊ฐ ์๊ณ , ๊ทธ๋ฐ๊ฒฝ์ฐ๋ผ๋ฉด ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ mysql์ค์น๋ฅผ ํด์ผํฉ๋๋ค.
๋ณดํต E2E๋ผ๊ณ ํ๋ฉด controller <โ> db ๊น์ง์ ํ ์คํธ๋ผ๊ณ ์๊ฐํ ์ ์์ง๋ง, ์ข ๋ ํฌ๊ฒ E2E๋ฅผ ๋ณธ๋ค๋ฉด ํ๋ก ํธ์๋ <โ> ๋ฐฑ์๋๊น์ง์ ๋ฒ์๋ผ๊ณ ๋ณผ ์ ์์ต๋๋ค.
๊ทธ๋ฐ๊ฒฝ์ฐ ํ๋ก ํธ์์ ํน์ ๋ฒํด์ ๋๋ฅด๊ณ api ๊ฐ ํธ์ถ๋์ด db์ ๋ฐ์ดํฐ๊ฐ ๋ค์ด๊ฐ๋ ๊ฒ ๊น์ง ํ ์คํธํ ์ ์์ต๋๋ค.
(js์์ ์ฌ์ฉํ๋ ๋๊ตฌ๋ก๋ cypress์ ๊ฐ์๊ฒ ์์ต๋๋ค.)
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 ์คํ -
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 ์คํ