[2024.06.14 TIL] 내일배움캠프 42일차 (개인 과제 리팩토링, 해설 강의 시청, 이력서 테스트 코드 작성)

My_Code·2024년 6월 16일
0

TIL

목록 보기
54/112
post-thumbnail

본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.
(작성한 코드의 양이 너무 많아 이력서 지원 상태 변경 기능에 대한 테스트 코드로 설명)


💻 TIL(Today I Learned)

📌 Today I Done

✏️ 이력서 지원상태 변경 테스트 코드 작성(Repository)

  • 이력서 지원상태 변경 기능은 Recruiter가 지원자의 이력서에 대한 평가를 내리는 기능임

  • 이 기능은 다른 기능들과 다르게 2가지 종류의 테이블에 대한 접근이 같이 이뤄져야 함

  • 그렇기에 Prisma의 Transaction문법을 사용해서 한 트랜젝션에서 2가지 일이 처리되도록 구현함

  • 마찬가지로 테스트 코드에서도 트랜젝션이 동작하는 것처럼 구현해줘야 함

  • 이력서 지원상태 변경 실행 코드

// src/repositories/resume.repository.js

    // 이력서 상태 변경
    updateResumeState = async (userId, resumeId, oldState, newState, reason) => {
        let resumeLog; // 이력서 변경 로그

        // 트랜젝션을 통해서 작업의 일관성 유지
        await this.prisma.$transaction(
            async (tx) => {
                // 이력서 수정
                await tx.resume.update({ where: { resumeId: +resumeId }, data: { state: newState } });

                // 이력서 변경 로그 생성
                resumeLog = await tx.resumeHistory.create({
                    data: { recruiterId: +userId, resumeId, oldState, newState, reason },
                });
            },
            {
                isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted,
            },
        );

        return resumeLog;
    };
  • 이력서 지원상태 변경 테스트 코드
// __tests__/unit/repositories/resume.repository.unit.spec.js

const mockPrisma = {
    resume: {
        create: jest.fn(),
        findMany: jest.fn(),
        findFirst: jest.fn(),
        update: jest.fn(),
        delete: jest.fn(),
    },
    resumeHistory: {
        create: jest.fn(),
        findMany: jest.fn(),
    },
    $transaction: jest.fn(),
};

...

    // 이력서 상태 변경
    test('updateResumeState Method', async () => {
        /* 설정 부분 */
        // Resume Repository의 updateResumeState 메서드 매개변수 임시값
        const updateResumeStateParams = {
            userId: 2,
            resumeId: 22,
            oldState: RESUME_CONSTANT.RESUME_STATE.APPLY,
            newState: RESUME_CONSTANT.RESUME_STATE.PASS,
            reason: '서류 통과',
        };
        // 모킹된 Prisma의 update 메서드의 임시 반환값
        const resumeLogSample = {
            resumeLogId: 7,
            resumeId: updateResumeStateParams.resumeId,
            recruiterId: updateResumeStateParams.userId,
            oldState: RESUME_CONSTANT.RESUME_STATE.APPLY,
            newState: RESUME_CONSTANT.RESUME_STATE.PASS,
            reason: updateResumeStateParams.reason,
            createdAt: new Date(),
        };
        // 모킹된 Prisma의 create 메서드의 결과값 설정
        mockPrisma.resumeHistory.create.mockReturnValue(resumeLogSample);

        // 모킹된 Prisma의 $transaction 메서드의 결과값 설정
        mockPrisma.$transaction.mockImplementation(async (tx) => {
            return await tx(mockPrisma);
        });

        /* 실행 부분, 실제 Resume Repository의  updateResumeState 메서드 실행 */
        const resumeLog = await resumeRepository.updateResumeState(
            updateResumeStateParams.userId,
            updateResumeStateParams.resumeId,
            updateResumeStateParams.oldState,
            updateResumeStateParams.newState,
            updateResumeStateParams.reason,
        );

        /* 테스트(조건) 부분 */
        // 모킹된 Prisma $transaction 메서드가 1번만 실행되었는지 검사
        expect(mockPrisma.$transaction).toHaveBeenCalledTimes(1);

        // 모킹된 Prisma resume의 update 메서드가 1번만 실행되었는지 검사
        expect(mockPrisma.resume.update).toHaveBeenCalledTimes(1);
        // 모킹된 Prisma resume의 update 메서드가 매개변수와 함께 호출되었는지 검사
        expect(mockPrisma.resume.update).toHaveBeenCalledWith({
            where: { resumeId: updateResumeStateParams.resumeId },
            data: { state: updateResumeStateParams.newState },
        });

        // 모킹된 Prisma resume의 create 메서드가 1번만 실행되었는지 검사
        expect(mockPrisma.resumeHistory.create).toHaveBeenCalledTimes(1);
        // 모킹된 Prisma resume의 create 메서드가 매개변수와 함께 호출되었는지 검사
        expect(mockPrisma.resumeHistory.create).toHaveBeenCalledWith({
            data: {
                recruiterId: resumeLogSample.recruiterId,
                resumeId: resumeLogSample.resumeId,
                oldState: resumeLogSample.oldState,
                newState: resumeLogSample.newState,
                reason: resumeLogSample.reason,
            },
        });

        // updateResumeState 메서드의 실행 결과값과
        // 모킹된 Prisma resumeHistory의 create 메서드의 결과값이 같은지 검사
        expect(resumeLog).toEqual(resumeLogSample);
    });

✏️ 이력서 지원상태 변경 테스트 코드 작성(Service)

  • 이력서 지원상태 변경 실행 코드
// src/services/resume.service.js

    // 이력서 상태 변경
    updateResumeState = async (userId, resumeId, newState, reason) => {
        const whereCondition = { resumeId };
        // 이력서 ID가 일치한 이력서 조회
        const resume = await this.resumeRepository.getResumeDetail(whereCondition);
        if (!resume) throw new HttpError.NotFound(MESSAGES.RESUMES.COMMON.NOT_FOUND);

        const resumeLog = await this.resumeRepository.updateResumeState(
            userId,
            resumeId,
            resume.state,
            newState,
            reason,
        );

        return resumeLog;
    };
  • 이력서 지원상태 변경 테스트 코드
// __tests__/unit/services/resume.service.unit.spec.js

    // 이력서 상태 변경
    test('updateResumeState Method', async () => {
        /* 설정 부분 */
        // Resume Service의 updateResumeState 메서드 매개변수 임시값
        const updateResumeStateParams = {
            userId: 1,
            resumeId: 12,
            newState: RESUME_CONSTANT.RESUME_STATE.PASS,
            reason: '서류 통과',
        };
        // Resume Repository의 updateResume 메서드의 임시 반환값
        const resumeSample = {
            resumeId: 12,
            userId: 1,
            title: '스파르탄 자기소개',
            introduce: '열심히 화이팅 화이팅!! ',
            state: 'APPLY',
            createdAt: '2024-06-14T07:15:16.397Z',
            updatedAt: '2024-06-14T07:15:16.397Z',
        };
        // Resume Repository의 getResumeDetail 메서드의 결과값 설정
        mockResumeRepository.getResumeDetail.mockReturnValue(resumeSample);

        // Resume Repository의 updateResumeState 메서드의 임시 반환값
        const resumeLogSample = {
            resumeLogId: 7,
            resumeId: updateResumeStateParams.resumeId,
            recruiterId: updateResumeStateParams.userId,
            oldState: resumeSample.state,
            newState: updateResumeStateParams.newState,
            reason: updateResumeStateParams.reason,
            createdAt: new Date(),
        };
        // Resume Repository의 updateResumeState 메서드의 결과값 설정
        mockResumeRepository.updateResumeState.mockReturnValue(resumeLogSample);

        /* 실행 부분, 실제 Resume Service의  updateResumeState 메서드 실행 */
        const resumeLog = await resumeService.updateResumeState(
            updateResumeStateParams.userId,
            updateResumeStateParams.resumeId,
            updateResumeStateParams.newState,
            updateResumeStateParams.reason,
        );

        /* 테스트(조건) 부분 */
        // Resume Repository의 getResumeDetail 메서드가 1번만 실행되었는지 검사
        expect(mockResumeRepository.getResumeDetail).toHaveBeenCalledTimes(1);
        // Resume Repository의 getResumeDetail 메서드가 매개변수와 함께 호출되었는지 검사
        expect(mockResumeRepository.getResumeDetail).toHaveBeenCalledWith({
            resumeId: updateResumeStateParams.resumeId,
        });

        // Resume Repository의 updateResumeState 메서드가 1번만 실행되었는지 검사
        expect(mockResumeRepository.updateResumeState).toHaveBeenCalledTimes(1);
        // Resume Repository의 updateResumeState 메서드가 매개변수와 함께 호출되었는지 검사
        expect(mockResumeRepository.updateResumeState).toHaveBeenCalledWith(
            updateResumeStateParams.userId,
            updateResumeStateParams.resumeId,
            resumeSample.state,
            updateResumeStateParams.newState,
            updateResumeStateParams.reason,
        );

        // Resume Service의 updateResumeState 메서드 실행 결과값과
        // Resume Repository의 updateResumeState 메서드의 결과값이 같은지 검사
        expect(resumeLog).toEqual(resumeLogSample);
    });

✏️ 이력서 지원상태 변경 테스트 코드 작성(Controller)

  • 이력서 지원상태 변경 실행 코드
// src/controllers/resume.controller.js
    // 이력서 지원 상태 변경 기능
    updateResumeState = async (req, res, next) => {
        try {
            // 사용자 정보 가져옴
            const { userId } = req.user;
            // 이력서 ID 가져옴
            const { resumeId } = req.params;
            //지원 상태, 사유 가져옴
            const { state, reason } = req.body;

            // 이력서 상태 변경
            const resumeLog = await this.resumeService.updateResumeState(userId, +resumeId, state, reason);

            return res
                .status(HTTP_STATUS.CREATED)
                .json({ status: HTTP_STATUS.CREATED, message: MESSAGES.RESUMES.STATE.SUCCEED, data: { resumeLog } });
        } catch (err) {
            next(err);
        }
    };
  • 이력서 지원상태 변경 테스트 코드
// __tests__/unit/controllers/resume.controller.unit.spec.js

    // 이력서 상태 변경
    test('updateResumeState Method', async () => {
        /* 설정 부분 */
        // Resume Controller의 updateResumeState 메서드가 실행되기 위한 Body 입력값
        const updateResumeStateRequestBodyParams = {
            state: RESUME_CONSTANT.RESUME_STATE.PASS,
            reason: '서류 합격!!',
        };
        // Request의 body에 입력할 인자값 설정
        mockRequest.body = updateResumeStateRequestBodyParams;

        // Resume Controller의 updateResumeState 메서드가 실행되기 위한 params 입력값
        const updateResumeStateRequestParams = {
            resumeId: 1,
        };
        // Request의 params에 입력할 인자값 설정
        mockRequest.params = updateResumeStateRequestParams;

        // Resume Controller의 updateResumeState 메서드가 실행되기 위한 User 입력값
        const updateResumeStateRequestUserParams = {
            userId: 1,
            email: 'spartan@spartacodingclub.kr',
            name: '스파르탄',
            age: 28,
            gender: 'MALE',
            role: 'APPLICANT',
            profileImage: 'https://prismalens.vercel.app/header/logo-dark.svg',
            createdAt: '2024-06-13T08:53:46.951Z',
            updatedAt: '2024-06-13T08:53:46.951Z',
        };
        // Request의 user에 입력할 인자값 설정
        mockRequest.user = updateResumeStateRequestUserParams;

        // Resume Controller의 updateResumeState 메서드의 임시 반환값
        let resumeLogSample = {
            resumeLogId: 4,
            userName: '스파르탄',
            resumeId: 11,
            oldState: 'APPLY',
            newState: 'PASS',
            reason: '서류 심사 통과!',
            createdAt: new Date(),
        };

        // Resume Service의 updateResumeState 메서드 반환값을 설정
        mockResumeService.updateResumeState.mockResolvedValue(resumeLogSample);

        /* 실행 부분, Controller의 updateResumeState 메서드 실행 */
        await resumeController.updateResumeState(mockRequest, mockResponse, mockNext);

        /* 테스트(조건) 부분 */
        // Resume Service의 updateResumeState 메서드가 1번만 실행되었는지 검사
        expect(mockResumeService.updateResumeState).toHaveBeenCalledTimes(1);
        // Resume Service의 updateResumeState 메서드가 매개변수와 함께 호출되었는지 검사
        expect(mockResumeService.updateResumeState).toHaveBeenCalledWith(
            updateResumeStateRequestParams.resumeId,
            updateResumeStateRequestUserParams.userId,
            updateResumeStateRequestBodyParams.state,
            updateResumeStateRequestBodyParams.reason,
        );

        // Response의 status 메서드가 1번만 실행되었는지 검사
        expect(mockResponse.status).toHaveBeenCalledTimes(1);
        // Response의 status 메서드가 매개변수와 함께 호출되었는지 검사
        expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.CREATED);

        // Response의 json 메서드가 1번만 실행되었는지 검사
        expect(mockResponse.json).toHaveBeenCalledTimes(1);
        // Response의 json 메서드가 매개변수와 함께 호출되었는지 검사
        expect(mockResponse.json).toHaveBeenCalledWith({
            status: HTTP_STATUS.CREATED,
            message: MESSAGES.RESUMES.STATE.SUCCEED,
            data: { resumeLog: resumeLogSample },
        });
    });


📌 Tomorrow's Goal

✏️ Node.js 심화 개인과제 리팩토링

  • 해설 강의를 참고로 하여 코드 리팩토링 진행


📌 Today's Goal I Done

✔️ Node.js 심화 개인과제 리팩토링

  • 해설 강의 영상이 빨리 나와서 강의를 시청하면서 코드를 리팩토링함

  • 해설 강의를 보니 내가 테스트 코드에서 힘들어 했던 이유가 나왔음

  • 나는 3계층 분리를 할 때 주요 기능별 메서드로 나눈 것이 아니라 모든 기능별로 세세하게 나눴음

  • 그래서 테스트 코드 작성시 작성해야 하는 테스트 코드의 양이 많았고 더 복잡해 졌음

  • 해설 강의 시청을 끝내고 나는 모든 코드들을 리팩토링했고, 구현하지 못한 이력서 테스트 코드도 작성을 완료함


profile
조금씩 정리하자!!!

0개의 댓글