mockResolvedValueOnce
mockResolvedValueOnce
mockResolvedValueOnce(value)
는 Jest에서 비동기 함수(Mock 함수)가 특정 호출에서 한 번만 지정된 값을 반환하도록 설정하는 기능입니다.
따라서 한 번만 특정 값을 반환한 후, 다음 호출에서는 다른 값 반환 가능합니다.
사용 사례
mockResolvedValue
대신 mockResolvedValueOnce
을 사용하는 사례를 알아봅시다.
다음은 간단한 create API usecase 코드와 이에 대한 test code입니다.
use-case 코드
import {
BadRequestException,
Inject,
Injectable,
} from "@nestjs/common";
import { CreateTechRequestDto } from "../dto/request/create-tech.request.dto";
import { UpdateTechRequestDto } from "../dto/request/update-tech.request.dto";
import { ManageTechRepositoryPort } from "../port/output/manage-tech.repository.port";
@Injectable()
export class ManageTechUseCase {
constructor(
@Inject("ManageTechRepositoryPort")
private readonly manageTechRepositoryPort: ManageTechRepositoryPort,
) {}
...
async create(createTechRequestDto: CreateTechRequestDto) {
const duplicatedOne = await this.findOneByContent(
createTechRequestDto.content,
);
if (duplicatedOne) {
throw new BadRequestException("존재하는 기술입니다.");
}
return this.manageTechRepositoryPort.create(
createTechRequestDto,
);
}
...
}
테스트 코드
describe("create()", () => {
it("create() 호출시, Tech를 생성하고 생성한 Tech 반환", async () => {
// Given
const createdTech: TechDomain = new TechDomain({
id: 1,
content: "nestjs",
});
const createDto: CreateTechRequestDto = {
content: "nestjs",
};
mockRepository.findOneByContent.mockResolvedValue(
null,
);
mockRepository.create.mockResolvedValue(
createdTech,
);
// When
const result =
await manageTechUseCase.create(createDto);
// Then
expect(
mockRepository.findOneByContent,
).toHaveBeenCalledWith(createDto.content);
expect(mockRepository.create).toHaveBeenCalledWith(
createDto,
);
expect(result).toEqual(createdTech);
});
it("create() 호출시, requestDTO에 이미 존재하는 tech로 요청을 하면 400 에러 발생", async () => {
// Given
const createDto: CreateTechRequestDto = {
content: "nestjs",
};
const existingTech: TechDomain = new TechDomain({
id: 1,
content: "nestjs",
});
mockRepository.findOneByContent.mockResolvedValue(
existingTech,
);
mockRepository.create.mockResolvedValue(null); // create()가 호출되지 않는 것을 검증
// When & Then
await expect(
manageTechUseCase.create(createDto),
).rejects.toThrow(BadRequestException);
expect(
mockRepository.findOneByContent,
).toHaveBeenCalledWith(createDto.content);
expect(mockRepository.create).not.toHaveBeenCalled(); // 중복된 경우 create()가 호출되지 않아야 함
});
});
위 테스트 코드를 실행하게 되면 실패하게 됩니다. BadRequestException과 create가 동시에 실행되버리죠.
어떻게 두 코드가 동시에 실행되는 것일까요?
이유는 테스트 환경이 서로 영향을 주고 있어서입니다. 즉, 첫 번째 테스트가 실행되면서 Jest의 mock 상태가 유지되어 두 번째 테스트 실행에 영향을 미치고 있는 것입니다.
mockResolvedValue()가 Jest에서 공유되는 문제
mockRepository.findOneByContent.mockResolvedValue(null);
가 첫 번째 테스트에서 설정되었고, Jest의 mockResolvedValue()는 기본적으로 테스트 케이스 간 상태를 유지하므로 두 번째 테스트에서 null이 반환될 수도 있습니다.
이런식으로 서로 다른 두 테스트 코드가 충돌하여 영향을 주며 테스트를 실패합니다.
이를 해결하기 위해 mockResolvedValue()
대신 mockResolvedValueOnce()
를 사용하면 됩니다.
mockResolvedValueOnce
현재 mockResolvedValue()를 사용하고 있는데, 이는 테스트 전체에서 지속적으로 유지됩니다. 대신 mockResolvedValueOnce()를 사용하면 각 테스트마다 Mock 값을 한 번만 설정하도록 변경하면 문제가 해결될 수 있습니다.
따라서 다음과 같이 테스트코드를 변경하면 해결됩니다.
describe("ManageTechUseCase", () => {
beforeEach(() => {
jest.clearAllMocks(); // 모든 mock 초기화
});
describe("create", () => {
it("Tech를 생성하고 생성한 Tech 반환", async () => {
// Given
const createdTech: TechDomain = new TechDomain({
id: 1,
content: "nestjs",
});
const createDto: CreateTechRequestDto = {
content: "nestjs",
};
mockRepository.findOneByContent.mockResolvedValueOnce(null);
mockRepository.create.mockResolvedValueOnce(createdTech);
// When
const result = await manageTechUseCase.create(createDto);
// Then
expect(mockRepository.findOneByContent).toHaveBeenCalledWith(createDto.content);
expect(mockRepository.create).toHaveBeenCalledWith(createDto);
expect(result).toEqual(createdTech);
});
it("requestDTO에 이미 존재하는 tech로 요청을 하면 400 에러 발생", async () => {
// Given
const createDto: CreateTechRequestDto = {
content: "nestjs",
};
const existingTech: TechDomain = new TechDomain({
id: 1,
content: "nestjs",
});
mockRepository.findOneByContent.mockResolvedValueOnce(existingTech);
mockRepository.create.mockResolvedValueOnce(null); // create()가 호출되지 않는 것을 검증
// When & Then
await expect(manageTechUseCase.create(createDto)).rejects.toThrow(BadRequestException);
expect(mockRepository.findOneByContent).toHaveBeenCalledWith(createDto.content);
expect(mockRepository.create).not.toHaveBeenCalled(); // 중복된 경우 create()가 호출되지 않아야 함
});
});
});
추가로 beforeEach에서 jest.clearAllMocks를 해주면 이전 테스트의 mock 상태를 초기화해줄 수 있습니다.