jest
이전에 jest 간단 사용법에 대해 포스팅 한 적이 있다.
https://growth-coder.tistory.com/284
이 때는 express 프레임워크를 사용했을 때 단위 테스트를 작성해보았는데 이번에는 Nest 프레임워크를 사용해서 단위 테스트를 작성해보려고 한다.
jest 사용법은 크게 다르지 않고 의존성을 주입해주는 부분만 신경써주면 된다.
테스트 해 볼 코드
테스트 해 볼 코드를 만들기 위해 간단한 프로젝트를 만들었다.
단순히 유저를 저장하고 조회하는 기능만 갖춘 프로젝트이다.
데이터베이스는 메모리 sqlite를 사용할 예정이고 typeorm을 사용할 예정이다.
<User.entity.ts>
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
name: string;
@Column()
password: string;
}
<user.repository.spec.ts>
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { DataSource } from 'typeorm';
@Injectable()
export class UserRepository extends Repository<User> {
constructor(private dataSource: DataSource) {
super(User, dataSource.createEntityManager());
}
async createUser(
username: string,
name: string,
password: string,
): Promise<User> {
const user = this.create({ username, name, password });
return this.save(user);
}
async findByUserName(username: string): Promise<User> {
return this.findOne({ where: { username } });
}
}
Repository 계층 단위 테스트
이제 Repository 계층에 대해 단위 테스트를 진행해보자.
repository를 mocking하는 방법과 실제 데이터베이스를 붙이는 방법 모두 해 볼 예정이다.
Repository Mocking
typeORM을 사용 중이기 때문에 먼저 data source를 모킹해야 한다.
dataSource의 createEntityManager를 호출하기 때문에 해당 함수도 모킹해준다.
import { Test, TestingModule } from '@nestjs/testing';
import { DataSource } from 'typeorm';
import { UserRepository } from './user.repository';
import { User } from './user.entity';
describe('UserRepository', () => {
let userRepository: UserRepository;
const dataSource = {
createEntityManager: jest.fn(),
};
beforeAll(async () => {
// NestJS 테스트 모듈 설정
const module: TestingModule = await Test.createTestingModule({
providers: [
UserRepository,
{
provide: DataSource,
useValue: dataSource,
},
],
}).compile();
userRepository = module.get<UserRepository>(UserRepository);
});
});
dataSource를 모킹했기 때문에 실제 데이터베이스와 연동되지 않아도 repository 인스턴스를 생성할 수 있다.
이제 이 repository에서 테스트 해 볼 메소드를 모킹한 뒤 검증하면 된다.
다음은 UserRepository의 createUser 메소드를 테스트 해보는 코드이다.
describe('createUser', () => {
it('should create and return a user', async () => {
// User 생성
const userData = {
id: 1,
username: 'testuser',
password: 'password',
};
const user = new User();
Object.assign(user, userData);
// 모킹
jest.spyOn(userRepository, 'createUser').mockResolvedValue(user);
// 실행
const savedUser = await userRepository.createUser(
userData.username,
userData.password,
);
// 검증
expect(savedUser.id).toBe(user.id);
expect(savedUser.username).toBe(user.username);
expect(savedUser.password).toBe(user.password);
});
});
<user.repository.spec.ts>
import { Test, TestingModule } from '@nestjs/testing';
import { DataSource } from 'typeorm';
import { UserRepository } from './user.repository';
import { User } from './user.entity';
describe('UserRepository', () => {
let userRepository: UserRepository;
const dataSource = {
createEntityManager: jest.fn(),
};
beforeAll(async () => {
// NestJS 테스트 모듈 설정
const module: TestingModule = await Test.createTestingModule({
providers: [
UserRepository,
{
provide: DataSource,
useValue: dataSource,
},
],
}).compile();
userRepository = module.get<UserRepository>(UserRepository);
});
it('should be defined', () => {
expect(userRepository).toBeDefined();
});
describe('createUser', () => {
it('should create and return a user', async () => {
// User 생성
const userData = {
id: 1,
username: 'testuser',
password: 'password',
};
const user = new User();
Object.assign(user, userData);
// 모킹
jest.spyOn(userRepository, 'createUser').mockResolvedValue(user);
// 실행
const savedUser = await userRepository.createUser(
userData.username,
userData.password,
);
// 검증
expect(savedUser.id).toBe(user.id);
expect(savedUser.username).toBe(user.username);
expect(savedUser.password).toBe(user.password);
});
});
describe('findByUserName', () => {
it('should find a user by username', async () => {
// User 생성
const userData = {
id: 1,
username: 'user',
password: 'password',
};
const user = new User();
Object.assign(user, userData);
// 모킹
jest.spyOn(userRepository, 'findByUserName').mockResolvedValue(user);
// 실행
const findUser = await userRepository.findByUserName(userData.username);
// 검증
expect(findUser.id).toBe(user.id);
expect(findUser.username).toBe(user.username);
expect(findUser.password).toBe(user.password);
});
});
});
repository 계층의 단위 테스트 코드를 작성하는 과정에서 의문점이 들었다.
일반적으로 모킹은 의존성이 존재하는 요소에 대해 가짜 객체를 만드는 것인데
repository를 테스트 하기 위해서 repository를 모킹하는 것이 맞을까?
예를 들어 서비스 계층인 UserService에 대한 단위 테스트를 작성한다고 해보자.
이 때 진짜 UserRepository를 넣어서 만들게 되면 테스트 비용이 많이 들기 때문에 UserRepository를 모킹해서 제공하는 것이다.
이러한 점에서 UserRepository에 대한 단위 테스트를 작성하기 위해 UserRepository를 모킹하는 것이 이상하다는 생각이 들었다.
그래서 repository 계층에 한해서 실제 데이터베이스를 붙여서 테스트하는 것이 더 나을 것 같다는 생각을 하게 되었다.
실제 데이터베이스 붙여서 테스트
다음은 메모리 데이터베이스를 붙여서 테스트해보는 코드이다.
크게 달라진 코드는 없고 실제 DataSource를 넣어주고 repository에 대한 모킹이 없어졌다.
<user.repository.spec.ts>
describe('UserRepository', () => {
let userRepository: UserRepository;
let dataSource: DataSource;
beforeAll(async () => {
// NestJS 테스트 모듈 설정
const module: TestingModule = await Test.createTestingModule({
providers: [
UserRepository,
{
provide: DataSource,
useFactory: () => {
return new DataSource({
type: 'sqlite',
database: ':memory:', // 메모리 내 SQLite 사용
entities: [User],
synchronize: true, // 테스트 후 DB 스키마 자동 동기화
logging: false,
});
},
},
],
}).compile();
userRepository = module.get<UserRepository>(UserRepository);
dataSource = module.get<DataSource>(DataSource);
// 실제 DB 연결
await dataSource.initialize();
});
afterAll(async () => {
// 테스트 완료 후 데이터베이스 연결 종료
await dataSource.destroy();
});
beforeEach(async () => {
// 각 테스트 실행 전에 DB 초기화 (선택 사항)
await dataSource.getRepository(User).clear();
});
it('should be defined', () => {
expect(userRepository).toBeDefined();
});
describe('createUser', () => {
it('should create and return a user', async () => {
const userData = {
username: 'testuser',
password: 'password',
};
const user = new User();
Object.assign(user, userData);
const savedUser = await userRepository.createUser(
userData.username,
userData.password,
);
expect(savedUser.username).toBe(userData.username);
expect(savedUser.password).toBe(userData.password);
});
});
describe('findByUserName', () => {
it('should find a user by username', async () => {
const userData = {
username: 'testuser',
password: 'password',
};
const user = new User();
Object.assign(user, userData);
const savedUser = await userRepository.createUser(
userData.username,
userData.password,
);
await userRepository.createUser(userData.username, userData.password);
const findUser = await userRepository.findByUserName(userData.username);
expect(findUser.username).toBe(userData.username);
expect(findUser.password).toBe(userData.password);
});
});
});
댓글