nest의 test 폴더에 보면 app.e2e-spec.ts 파일이 있으며, 여기에 기본적인 e2e 테스트 코드가 작성되어있다.
내 경우 다른 강의에서 영감을 얻어 각 모듈/기능/폴더 별로 __test__ 라는 폴더를 만들고 그 안에 test 코드파일을 만들었고, 강의에서도 각 모듈별로 e2e테스트를 만들길 선호하셔서 그에 맞추려고한다.
- 세팅
테스트할 모듈쪽으로 옮기고 파일 이름을 변경 app.e2e-spec.ts => movie.e2e.spec.ts
jest-e2e.json 파일을 최상위로 옮기기, 기존 test 폴더는 필요없으니 삭제,
// jest-e2e.json
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
기본 파일 내용을
// jest-e2e.json
{
"moduleFileExtensions": ["js", "json", "ts"],
"roots": ["src"],
"testEnvironment": "node",
"testRegex": ".e2e.spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"moduleNameMapper": {
"src/(.*)": "<rootDir>/src/$1"
}
}
위 코드로 변경
testRegex의 이름 변경,
moduleNameMapper 추가,
rootDir을 root의 내용으로 변경
// package.json
"test:e2e": "jest --config ./test/jest-e2e.json",
test 파일에서 옮겼으므로
// package.json
"test:e2e": "jest --config ./jest-e2e.json",
위와 같이 test 제거한걸로 변경
+++
https://springdream0406.tistory.com/225
pg-mem Nest에 Test DB로 설정하기
pg-mem을 Nest 프로젝트의 Test DB로 적용하기로 한 이유https://springdream0406.tistory.com/224 Test Code 2 (작성 중)통합테스트 코드를 작성해본 후, 대부분을 mock으로 처리해놓은 현재 유닛테스트에 대한 의
springdream0406.tistory.com
e2e 테스트의 db를 memory db로 변경하고 테스트마다 생성한다면 아래의 DB연결 오류를 해결 할 수 있다.
++ e2e테스트를 여러개로 나누게 되면, 한번에 테스트를 돌렸을 때 병렬로 테스트를 진행하다보니 DB 연결에 대한 오류등이 나곤한다.
"test:e2e": "cross-env NODE_ENV=.test jest --runInBand --config ./jest-e2e.json",
때문에 위의 코드처럼 --runInBand를 추가하여 테스트가 병렬이 아니라 순차적으로 진행되도록 변경해주는게 좋다.
대신 시간은 좀 더 걸린다고 하는데 나는 아직 크게 체감이 될정도는 아니었다.
- 테스트용 DB를 만들어 사용할 경우
// .env.test
# dev = 개발 환경
# prod = 배포 환경
ENV=test
# DB
DB_TYPE=postgres
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=sp
DB_PASSWORD=
DATABASE=test
# Hash
HASH_ROUNDS=10
# JWT
ACCESS_TOKEN_SECRET=codefactory
REFRESH_TOKEN_SECRET=codefactory
env 파일을 복붙 하고 안의 내용을 변경하여 test용 .env.test 생성
+ gitignore에 .env.test 추가
// package.json
"test:e2e": "NODE_ENV=test jest --config ./jest-e2e.json",
명령어에 환경변수 넣어주기
CREATE DATABASE test;
pgAdmin에서 test라는 DB 생성해주기
// app.module.ts
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: validationSchema,
envFilePath: process.env.NODE_ENV === 'test' ? '.env.test' : '.env',
}),
envFilePath를 추가하고 앞서 환경변수로 넣어준걸로 삼항 걸어주기
(env파일 안의 내용으로 하면 못읽어와서 false를 실행해버리기 때문에 꼭 package.json 에서 명령어 환경변수를 넣어주고 그걸 가지고 사용하기!!)
// validation.schema.ts
ENV: Joi.string().valid('test', 'dev', 'prod').required(),
Joi를 사용하고, ENV valid를 걸었다면 test 추가해주기
++ OS간의 호환성 문제를 예방하기 위해 cross-env 설치해주기
yarn add cross-env
아래는 강의에서 작성한 e2e 테스트 코드이다.
토큰 발급을 위해 한번 선언하고만 것 같은 것들은 let말고 const써야할듯.
++ dto는 전에 만들었던것 그대로 사용할 수 있고, statusCode와 body의 inferfaace를 만들어서 넣어주면 expect 작성할 때 조금 더 편리하다.
++
https://springdream0406.tistory.com/220
Nest e2e test DB PK 초기화
어제부터 강의 따라서 열심히 프로젝트에 e2e 테스트를 적용하고 있다. 어제 auth 테이블을 마치고 오늘은 user 테이블을 작업중인데, 어제 auth 테이블에서 겪었던 문제가 다시 나타났다.바로 db의
springdream0406.tistory.com
// movie.e2e.spec.ts
import * as fs from 'fs/promises';
describe('MovieController (e2e)', () => {
let app: INestApplication;
// 테스트 데이터 넣기 세팅
let dataSource: DataSource;
let users: User[];
let directors: Director[];
let movies: Movie[];
let genres: Genre[];
let token: string; // 인증용
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
// 파이프 설정
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transformOptions: {
enableImplicitConversion: true,
},
}),
);
await app.init();
// 테스트 데이터 넣기 세팅
dataSource = app.get<DataSource>(DataSource);
const movieRepository = dataSource.getRepository(Movie);
const movieDetailRepository = dataSource.getRepository(MovieDetail);
const userRepository = dataSource.getRepository(User);
const directorRepository = dataSource.getRepository(Director);
const genreRepository = dataSource.getRepository(Genre);
const movieUserLikeRepository = dataSource.getRepository(MovieUserLike);
// 테스트 데이터 넣기 전에 빈상태로 만들기
await movieRepository.delete({});
await movieDetailRepository.delete({});
await userRepository.delete({});
await directorRepository.delete({});
await genreRepository.delete({});
await movieUserLikeRepository.delete({});
// 테스트 데이터 넣기
users = [1, 2].map((x) =>
userRepository.create({
id: x,
email: `${x}@test.com`,
password: `123123`,
}),
);
await userRepository.save(users);
directors = [1, 2].map((x) =>
directorRepository.create({
id: x,
dob: new Date('1992-11-23'),
nationality: 'South Korea',
name: `Director Name ${x}`,
}),
);
await directorRepository.save(directors);
genres = [1, 2].map((x) =>
genreRepository.create({
id: x,
name: `Genre ${x}`,
}),
);
await genreRepository.save(genres);
movies = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15].map((x) =>
movieRepository.create({
id: x,
title: `Movie ${x}`,
creator: users[0],
genres: genres,
likeCount: 0,
dislikeCount: 0,
detail: movieDetailRepository.create({
detail: `Movie Detail ${x}`,
}),
movieFilePath: 'moves/movie1.mp4',
director: directors[0],
createdAt: new Date(`2023-9-${x}`),
}),
);
await movieRepository.save(movies);
// 인증용 토큰 생성
let authService = moduleFixture.get<AuthService>(AuthService);
token = await authService.issueToken(
{ id: users[0].id, role: Role.admin },
false,
);
});
afterAll(async () => {
await new Promise((resolve) => setTimeout(resolve, 500)); // 바로 서버 닫으면 권고(노란) 로그가 뜨기 때문에 이를 방지하기 위해 기다렸다 닫음
await dataSource.destroy(); // db 삭제
await app.close(); // 서버 닫기
});
describe('[GET /movie]', () => {
it('should get all movies', async () => {
const { body, statusCode } = await request(app.getHttpServer()) // 에러 보고 싶으면 {}에 error 추가하고 log 찍기
.get('/movie');
expect(statusCode).toBe(200);
expect(body).toHaveProperty('data');
expect(body).toHaveProperty('nextCursor');
expect(body).toHaveProperty('count');
expect(body.data).toHaveLength(5);
});
});
describe('[GET /movie/recet]', () => {
it('should get recent movies', async () => {
const { body, statusCode } = await request(app.getHttpServer())
.get('/movie/recent')
.set('authorization', `Bearer ${token}`); // 인증용 토큰 넣기
expect(statusCode).toBe(200);
expect(body).toHaveLength(10);
});
});
describe('[GET /movie/{id}', () => {
it('should get movie by id', async () => {
const movieId = movies[0].id;
const { body, statusCode } = await request(app.getHttpServer())
.get(`/movie/${movieId}`)
.set('authorization', `Bearer ${token}`);
expect(statusCode).toBe(200);
expect(body.id).toBe(movieId);
});
it('should throw 404 error if movie does not exist', async () => {
const movieId = 99999;
const { body, statusCode } = await request(app.getHttpServer())
.get(`/movie/${movieId}`)
.set('authorization', `Bearer ${token}`);
expect(statusCode).toBe(404);
});
});
describe('[POST /movie]', () => {
it('should create movie', async () => {
// movieFileName을 mock처리 해도 되고, 실제로 요청해서 받아와도 되고.
const {
body: { fileName },
} = await request(app.getHttpServer())
.post('/common/video')
.set('authorization', `Bearer ${token}`)
.attach('video', Buffer.from('test'), 'movie.mp4') // 가상의 파일 첨부
.expect(201);
const dto: CreateMovieDto = {
title: 'Test Movie',
detail: 'Test Movie Detail',
directorId: directors[0].id,
genreIds: genres.map((x) => x.id),
movieFileName: fileName,
};
const { body, statusCode } = await request(app.getHttpServer())
.post('/movie')
.set('authorization', `Bearer ${token}`)
.send(dto); // front에서 보내는 body 넣기
expect(statusCode).toBe(201);
expect(body).toBeDefined();
expect(body.title).toBe(dto.title);
expect(body.detail.detail).toBe(dto.detail);
expect(body.director.id).toBe(dto.directorId);
expect(body.genres.map((x) => x.id)).toEqual(dto.genreIds);
expect(body.movieFilePath).toContain(fileName);
// 파일 쌓이는거 방지하기 위해 파일 삭제 코드 추가함 (fs = File System)
// import * as fs from 'fs/promises';
await fs
.unlink(`public/movie/${fileName}`)
.catch((err) => console.error(`Error deleting file ${err}`));
});
});
describe('[PATCH /movie/{id}', () => {
it('should update movie if exists', async () => {
const dto: UpdateMovieDto = {
title: 'Updated Test Movie',
detail: 'Updated Test Movie Detail',
directorId: directors[0].id,
genreIds: [genres[0].id],
};
const movieId = movies[0].id;
const { body, statusCode } = await request(app.getHttpServer())
.patch(`/movie/${movieId}`)
.set('authorization', `Bearer ${token}`)
.send(dto);
expect(statusCode).toBe(200);
expect(body).toBeDefined();
expect(body.title).toBe(dto.title);
expect(body.detail.detail).toBe(dto.detail);
expect(body.director.id).toBe(dto.directorId);
expect(body.genres.map((x) => x.id)).toEqual(dto.genreIds);
});
});
describe('[DELETE /movie/{id}', () => {
it('should delete existing movie', async () => {
const movieId = movies[0].id;
const { body, statusCode } = await request(app.getHttpServer())
.delete(`/movie/${movieId}`)
.set('authorization', `Bearer ${token}`);
expect(statusCode).toBe(200);
});
it('should throw 404 error if movie does not exist', async () => {
const movieId = 99999;
const { body, statusCode } = await request(app.getHttpServer())
.delete(`/movie/${movieId}`)
.set('authorization', `Bearer ${token}`);
expect(statusCode).toBe(404);
});
});
describe('[POST /movie/{id}like]', () => {
it('should like a movie', async () => {
// beforAll로 데이터를 넣은 상태에서 실행중이기 때문에 db상태가 연동되어있는 중임.
// 때문에 0번이 아닌 1번을 넣음.
// beforeEach로 해야하지만 그러면 db 넣는 작업 계속 해서 오래걸리기 때문에 beforeAll을 함.
const movieId = movies[1].id;
const { body, statusCode } = await request(app.getHttpServer())
.post(`/movie/${movieId}/like`)
.set('authorization', `Bearer ${token}`);
expect(statusCode).toBe(201);
expect(body).toBeDefined();
expect(body.isLike).toBe(true);
});
it('should cancellike a movie', async () => {
const movieId = movies[1].id;
const { body, statusCode } = await request(app.getHttpServer())
.post(`/movie/${movieId}/like`)
.set('authorization', `Bearer ${token}`);
expect(statusCode).toBe(201);
expect(body).toBeDefined();
expect(body.isLike).toBe(null);
});
});
describe('[POST /movie/{id}dislike]', () => {
it('should dislike a movie', async () => {
const movieId = movies[1].id;
const { body, statusCode } = await request(app.getHttpServer())
.post(`/movie/${movieId}/dislike`)
.set('authorization', `Bearer ${token}`);
expect(statusCode).toBe(201);
expect(body).toBeDefined();
expect(body.isLike).toBe(false);
});
it('should canceldislike a movie', async () => {
const movieId = movies[1].id;
const { body, statusCode } = await request(app.getHttpServer())
.post(`/movie/${movieId}/dislike`)
.set('authorization', `Bearer ${token}`);
expect(statusCode).toBe(201);
expect(body).toBeDefined();
expect(body.isLike).toBe(null);
});
});
});
03:43 delete인데 patch로 요청
'코딩 > Nest.js' 카테고리의 다른 글
Testing - Integration Test (통합 테스트) (0) | 2024.11.09 |
---|---|
Testing - Unit Test (단위 테스트) (0) | 2024.11.05 |
Testing - 개념과 초기 세팅 (3) | 2024.11.03 |
Swagger 2 (0) | 2024.10.27 |
Versioning (0) | 2024.10.27 |