1. Private 를 모킹 해야하나 말아야하나 ??
1. 모킹이 필요한 경우
모킹이 필요한 이유
- 외부 종속성 차단: 테스트 대상 코드가 데이터베이스, API 호출, 파일 시스템 등 외부 시스템에 의존한다면, 이러한 외부 종속성을 차단하여 테스트를 독립적으로 실행할 수 있도록 하기 위해 모킹이 필요합니다.
- 테스트 속도: 실제 데이터베이스를 호출하거나 HTTP 요청을 보내는 작업은 느리고 불안정할 수 있습니다. 모킹은 이러한 문제를 피할 수 있습니다.
- 예외 상황 시뮬레이션: 예를 들어, 데이터베이스가 비정상적으로 작동하거나 네트워크 오류가 발생하는 상황을 테스트하려면, 모킹이 필수적입니다.
- 상태 제어: 모킹을 통해 테스트에 필요한 특정 상태(예: 특정 데이터를 가진 사용자)를 쉽게 설정할 수 있습니다.
2. 모킹이 필요하지 않은 경우
실제 구현을 사용할 수 있는 경우
- 단순 비즈니스 로직 테스트: 코드가 외부 시스템에 의존하지 않는다면, 실제 구현을 사용하는 테스트로 충분할 수 있습니다.
- 예: verifyCode처럼 단순히 매개변수 비교를 수행하는 경우.
- 엔드 투 엔드(E2E) 테스트: 전체 시스템의 동작을 검증하는 E2E 테스트에서는 실제 데이터베이스, API 등을 사용하여 시스템의 전체 흐름을 확인합니다.
- 이 경우, 모킹은 테스트 목적과 반대되므로 사용하지 않습니다.
private async verifyCode(code: string, req: Request): Promise<boolean> {
return code === req.session.code;
}
- 모킹이 필요하지 않은 경우: 단순한 비즈니스 로직(외부 종속성이 없는 경우)을 테스트할 때.
- 모킹이 필요한 경우: 데이터베이스, 파일 시스템, 외부 API 등 외부 시스템과 상호작용이 있는 경우.
따라서, verifyCode 같은 단순한 로직에서는 모킹이 필수가 아니지만, 복잡한 의존성을 가지는 메서드(예: deleteUserAccount)에서는 모킹이 필요합니다.
2.await this.findChannelByUserId(user.id); 를 where: { user: { id: mockUser.id } },이런식으로 사용해야한다.
async updateVideo(
user: UserEntity,
videoId: number,
updateVideoDto: UpdateVideoDto,
): Promise<VideoEntity> {
await this.findChannelByUserId(user.id);
const foundVideo = await this.findVideoById(videoId);
const updateData = await this.updateDetails(updateVideoDto, foundVideo);
await this.videoRepository.update({ id: videoId }, updateData);
const updatedVideo = await this.videoRepository.findOne({ where: { id: videoId } });
return updatedVideo;
}
it('수정된 비디오 메타데이터를 반환한다.', async () => {
mockChannelRepository.findOne.mockResolvedValue(mockChannel);
mockVideoRepository.findOne.mockResolvedValue(mockVideo);
mockVideoRepository.update.mockResolvedValue(undefined);
mockVideoRepository.findOne.mockResolvedValue(mockUpdatedVideo);
const result = await videoService.updateVideo(mockUser, 1, mockUpdateVideoDto);
expect(channelRepository.findOne).toHaveBeenLastCalledWith({
where: { user: { id: mockUser.id } },
});
expect(videoRepository.findOne).toHaveBeenCalledWith({ where: { id: 1 } });
expect(videoRepository.update).toHaveBeenCalledWith(
{ id: 1 },
{
title: mockUpdateVideoDto.title,
description: mockUpdateVideoDto.description,
thumbnailUrl: mockUpdateVideoDto.thumbnailUrl,
hashtags: mockUpdateVideoDto.hashtags,
visibility: mockUpdateVideoDto.visibility,
},
);
expect(result).toEqual(mockUpdatedVideo);
});
{ user: { id: mockUser.id } }와 (user.id)와 같은 접근 방식의 차이는 데이터베이스 관계와 **ORM(Query)**에서의 동작 방식 때문입니다. 이 차이는 ORM 모델링 구조와 쿼리 빌더 작성 규칙에 따라 달라집니다.
1. { user: { id: mockUser.id } }가 필요한 이유
NestJS에서 TypeORM과 같은 ORM을 사용할 때, 연관 관계가 설정된 엔티티를 조회하거나 필터링하는 경우, 객체 형태로 경로를 지정해야 합니다.
예시: TypeORM 엔티티 관계
@Entity()
export class ChannelEntity {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne(() => UserEntity, (user) => user.channels)
user: UserEntity; // 관계 필드
}
데이터베이스 구조:
- ChannelEntity는 UserEntity와 ManyToOne 관계를 가지므로, TypeORM은 내부적으로 userId 필드를 사용해 연결합니다.
- 하지만 TypeORM 쿼리는 userId를 직접 사용하지 않고, 객체 구조를 통해 관계를 표현합니다.
2. 왜 단순히 (user.id)를 사용하지 않는가?
(user.id)는 관계를 명시적으로 정의하지 않으므로, TypeORM에서 아래 두 가지 문제가 발생할 수 있습니다:
- 직접 필드를 참조하려면 명시적 데이터베이스 필드 이름을 사용해야 함
- TypeORM에서 user.id처럼 관계를 간접적으로 사용하는 경우, ORM은 관계를 제대로 인식하지 못합니다.
3. 언제 (user.id)를 사용할 수 있나?
(user.id)처럼 단순히 userId 필드를 직접 사용하는 경우는 관계 설정이 없는 경우에만 가능합니다. 예를 들어, 아래와 같이 테이블에 직접적인 외래 키(userId)만 존재하고, TypeORM 관계가 설정되지 않은 경우:
@Entity()
export class ChannelEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
userId: number; // 단순 필드로만 존재
}
channelRepository.findOne({
where: { userId: mockUser.id }, // 가능 (단순 외래 키 필드 접근)
});
하지만 이 방식은 ORM의 장점을 제대로 활용하지 못하고, 관계를 표현하지 못하므로 권장되지 않습니다.
4. 결론
- ORM 관계가 설정된 경우:
where: { user: { id: mockUser.id } }
직접 필드를 사용하는 경우:
where: { userId: mockUser.id }
'Daily Logs > TIL (Today I Learned)' 카테고리의 다른 글
const로 선언된 변수는 재할당이 불가능하다. (0) | 2024.12.11 |
---|---|
숏폼 프로젝트 ES LINT , prettier 설정 및 트러블 슈팅 (0) | 2024.12.08 |
JavaScript deep dive 변수 선언의 실행 시점과 변수 호이스팅 (0) | 2024.11.27 |
스웨거 활용법 (0) | 2024.11.08 |
와이어 프레임의 중요성 (1) | 2024.10.21 |