💡테스트코드의 중요성
코드의 변화가 있을 때마다, 플레이그라운드를 통해 직접 모든 기능을 체크해보는 것은 효율적이지 않고 빈틈이 있을 수 있으므로 테스트코드가 필요❗
1) unit test
beforeEach: Testing 이전에 실행되는 부분
describe: 여러개의 테스트를 모아놓은 그룹단위
it(test): 하나의 테스트 단위 (it들은 모두 독립적)
[하나의 테스트 예시]
// it(테스트 이름 , 실행 함수)
it('더하기 테스트', () => {
const a = 1;
const b = 2;
expect(a + b).toBe(3); // expext()의 내용이 3이 될 것이라고 기대한다!!!
});
[여러개 묶음으로 테스트하는 예시]
// describe(테스트 이름, 실행할 함수들)
describe('나의 테스트 그룹', () => {
it('더하기 테스트', () => {
const a = 1;
const b = 2;
expect(a + b).toBe(3); //()의 내용이 3이 될 것이라고 기대한다!!!
});
it('곱하기 테스트', () => {
const a = 1;
const b = 2;
expect(a * b).toBe(2); //()의 내용이 2이 될 것이라고 기대한다!!!
});
💡 테스트 코드가 많으면 많을 수록 디테일하게 검증할 수 있지만 얼마나 자세하게 검증할지는 고려 필요(처음부터 너무 완벽하게 설계X, 점차적으로 추가하는 것이 좋음)
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
// 전역변수로 사용하기 위해 선언
let appController: AppController; // 타입같이 선언
let appService: AppService;
// 사전 작업할 내용
beforeEach(() => {
// 직접 의존성 주입(AppService,AppController 연결)
appService = new AppService();
appController = new AppController(appService); // constructor주입 필요
});
// describe("api이름",() => {})
describe('getHello', () => {
it('이 테스트의 검증 결과는 Hello World를 리턴해야함!!', () => {
const result = appController.getHello();
expect(result).toBe('Hello World!'); // result가 Hello World니?
});
});
});
📌beforeEach에 app.module.ts와 같이 appModule이라는 TestingModule이용하기
yarn add @nestjs/testing
설치import { Test } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppModule } from './app.module';
import { AppService } from './app.service';
describe('AppController', () => {
// 전역변수로 사용하기 위해 선언
let appController: AppController; // 타입같이 선언
//let appService: AppService;
// 사전 작업할 내용
beforeEach(async () => {
// Module을 통해 의존성 주입
// nestjs testing라이브러리
const appModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
// Module을 통해 AppController 가져오기
appModule.get<AppController>(AppController);
// AppService는 자동으로 주입
//appService = new AppService();
appController = appModule.get<AppController>(AppController);
});
// describe("api이름",() => {})
describe('getHello', () => {
it('이 테스트의 검증 결과는 Hello World를 리턴해야함!!', () => {
const result = appController.getHello();
expect(result).toBe('Hello World!'); // result가 Hello World니?
});
});
});
📌Unit Test시 실제 코드가 실행되는 환경과 같은 환경 조성 필요
import { Test } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppModule } from './app.module';
import { AppService } from './app.service';
// 가짜 AppService생성
class MockAppService {
// 함수명은 동일해야함
getHello() {
// 가짜 DB로 접속하도록 코드 변경
return 'Hello World!';
}
}
describe('AppController', () => {
// 전역변수로 사용하기 위해 선언
let appController: AppController; // 타입같이 선언
//let appService: AppService;
// 사전 작업할 내용
beforeEach(async () => {
// Module을 통해 의존성 주입
// nestjs testing라이브러리
const appModule = await Test.createTestingModule({
controllers: [AppController],
providers: [
{
provide: AppService, // 원본
useClass: MockAppService, // 나만의 AppService 주입하기 ( dependency injection의 장점 - 실제 DB에 접근하는 것을 막을 수 있음 )
},
], // 원본으로 주입하면 실제 DB에 접근하기 때문에 가짜 AppService 생성해서 주입필요 = Mock
}).compile();
// Module을 통해 AppController 가져오기
appModule.get<AppController>(AppController);
// AppService는 자동으로 주입
//appService = new AppService();
appController = appModule.get<AppController>(AppController);
});
// describe("api이름",() => {})
describe('getHello', () => {
it('이 테스트의 검증 결과는 Hello World를 리턴해야함!!', () => {
const result = appController.getHello();
expect(result).toBe('Hello World!'); // result가 Hello World니?
});
});
});
🔎 Testing시 실제 Repository를 사용하여 동일한 환경을 만들어준다면 실제 DB에 데이터가 들어가게 되어 심각한 오류 발생
그렇다면 Service test시 DB에 접근하는 경우는 어떻게 해야할까?🤔
1. 실제 DB에 등록해도될까? => 사용XXXX
2. 가짜 DB를 만들어 놓고 거기다 등록?
=> test시간이 오래걸림 / 접속문제(네트워크 문제로 접속이 안될 수 있음)
3. 가짜 DB를 javascript로 만들고, 거기다 push해도 될까? => 가장 많이 사용❗❗❗
[예시]
import { ConflictException } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entities/user.entity';
import { UserService } from '../user.service';
class MockUserRepository {
mydb = [
{ email: 'a@a.com', password: '0000', name: '짱구', age: 8 }, //
];
findOne({ email }) {
const users = this.mydb.filter((el) => el.email === email);
if (users.length) return users[0];
return null;
}
save({ email, password, name, age }) {
this.mydb.push({ email, password, name, age });
return { email, password, name, age };
}
}
type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>; // repository 타입
// keyof
// const IProfile = {
// name: '철수',
// age: 13,
// };
// const qqq: keyof IProfile; === const qqq: 'name' | 'age'; key만 뽑아줌
describe('UserService', () => {
let userService: UserService;
let userRepository: MockRepository<User>;
beforeEach(async () => {
const userModule = await Test.createTestingModule({
providers: [
UserService,
//repository 등록
{
provide: getRepositoryToken(User),
useClass: MockUserRepository,
},
],
}).compile();
userService = userModule.get<UserService>(UserService);
userRepository = userModule.get<MockRepository<User>>(
getRepositoryToken(User),
);
});
describe('create', () => {
it('이미 존재하는 이메일 검증', async () => {
const userRepositorySpyFindOne = jest.spyOn(userRepository, 'findOne'); // 몇번 시행되었는지 체크
const userRepositorySpySave = jest.spyOn(userRepository, 'save');
//userRepository만 mockRepository로 바뀌어서 진행
const myData = {
email: 'a@a.com',
hashedPassword: '1234',
name: '철수',
age: 13,
};
try {
await userService.create({ ...myData });
console.log('AAAAA');
} catch (error) {
console.log('BBBBB');
expect(error).toBeInstanceOf(ConflictException);
}
expect(userRepositorySpyFindOne).toBeCalledTimes(1);
expect(userRepositorySpySave).toBeCalledTimes(0);
});
it('회원 등록 잘됐는지 검증!', async () => {
const userRepositorySpyFindOne = jest.spyOn(userRepository, 'findOne'); // 몇번 시행되었는지 체크
const userRepositorySpySave = jest.spyOn(userRepository, 'save');
const myData = {
email: 'bbb@bbb.com',
hashedPassword: '1234',
name: '철수',
age: 13,
};
const myResultData = {
email: 'bbb@bbb.com',
password: '1234',
name: '철수',
age: 13,
};
const result = await userService.create({ ...myData });
expect(result).toStrictEqual(myResultData); // 객체, 배열 비교 시 엄격하게 비교 필요
expect(userRepositorySpyFindOne).toBeCalledTimes(1);
expect(userRepositorySpySave).toBeCalledTimes(1);
});
});
//describe('findOne', () => {});
});