ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • nodejs 환경에서 jest를 이용해 미들웨어를 test해보자(ts)
    카테고리 없음 2021. 10. 17. 15:12
    npm i -D jest ts-jest @types/jest

    서비스가 커져감에 따라서 이제 손으로 테스트를 하는데는 한계가 찾아왔다.

    수많은 라우터와 미들웨어, 함수들이 존재해서 버전업이 될때마다 하나하나 확인을 하기 힘들었다.

    그래서 그동안 서버응답여부만 체크하던 jest를 더 확장해서 모든 기능들을 테스트하기로 결정했다.

     

     

    우선 jest를 설치하자.

     

    필요한 패키지는 다음과 같다.

     

    npm i -D jest ts-jest @types/jest

     

    그리고 jest.config.js 파일을 생성한다.

    /** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
    module.exports = {
        preset: 'ts-jest',
        testEnvironment: 'node',
        moduleNameMapper: {
            '^@src/(.*)$': '<rootDir>/src/$1',
            '^@db$': '<rootDir>/src/db.ts',
            '^@middlewares/(.*)$': '<rootDir>/src/middlewares/$1',
            '^@routers/(.*)$': '<rootDir>/src/routers/$1',
            '^@services/(.*)$': '<rootDir>/src/services/$1',
        },
    };

    preset과 testEnvironment는 그대로, moduleNameMapper는 본인의 프로젝트에 따라서 적절하게 작성하면 된다.

     

     

     

    그럼 로그인 여부를 판단하는 미들웨어인 isLoggedIn, isNotLoggedIn 미들웨어를 테스트하는 코드를 작성해보자.

    일단, 우리의 미들웨어는 다음처럼 생겼다.

    export const isLoggedIn: expressMiddleware = (req: Request, res: Response, next: NextFunction) => {
        if (req.isAuthenticated()) {
            return next();
        }
        return res.status(403).json({
            info: 'Login Required',
        });
    };
    
    export const isNotLoggedIn: expressMiddleware = (req: Request, res: Response, next: NextFunction) => {
        if (!req.isAuthenticated()) {
            return next();
        }
        return res.status(403).json({
            info: 'Account is already logged in',
        });
    };

     

     

    이제 test폴더를 만들고, 그 안에 middleware 폴더를 만든 뒤 파일을 하나 생성해준다.

    우리의 경우 파일명을 auth.test.ts로 정했다. 위의 미들웨어가 src/middleware/auth.ts 경로에 존재하기 때문이다.

    우선 완성된 코드를 보자.

    import { mockRequest, mockResponse } from 'jest-mock-req-res';
    import { isLoggedIn, isNotLoggedIn } from '../../src/middlewares/auth';
    
    describe('isLoggedIn', () => {
        const res: MockResponse = mockResponse({});
        const next: () => void = jest.fn();
        test('로그인이 되어있으면 isLoggedIn이 next를 호출해야 함', () => {
            const req: MockRequest = mockRequest({
                isAuthenticated: jest.fn(() => true),
            });
            isLoggedIn(req, res, next);
            expect(next).toBeCalledTimes(1);
        });
    
        test('로그인이 되어있지 않으면 isLoggedIn이 403에러를 응답해야 함', () => {
            const req: MockRequest = mockRequest({
                isAuthenticated: jest.fn(() => false),
            });
            isLoggedIn(req, res, next);
            expect(res.status).toBeCalledWith(403);
            expect(res.json).toBeCalledWith({
                info: 'Login Required',
            });
        });
    });

    describe는 test를 그룹화해주는 역할이고, test 함수가 실행되면서 expect와 일치하는지 확인을 하게 된다.

    expect뒤의 메서드들은 jest 공식문서에 잘 나와있다.

     

    res, req의 mock객체가 필요한데, 이는 다음 패키지로 해결 가능하다.

    npm i -D jest-mock-req-res

    우리는 ts를 사용중이니, 이 mock객체의 타입도 알아야한다.

    mockRequest와 mockResponse의 정의를 찾아가보면, MockRequest와 MockResponse라는 인터페이스가 선언된 것을 확인할 수 있다. 그런데 export가 되어있지 않다..! 따라서 해당 내용을 복사해서 따로 타입 정의를 해주어야 한다.

     

    src/@types/req-res.d.ts 파일을 생성하고 해당하는 인터페이스들을 복사해서 작성해준다.

    /// <reference types="jest" />
    import { Response, Request } from 'express';
    
    declare global{
        interface MockResponse extends Response {
            append: jest.Mock;
            attachment: jest.Mock;
            clearCookie: jest.Mock;
            contentType: jest.Mock;
            cookie: jest.Mock;
            download: jest.Mock;
            end: jest.Mock;
            format: jest.Mock;
            get: jest.Mock;
            header: jest.Mock;
            json: jest.Mock;
            jsonp: jest.Mock;
            links: jest.Mock;
            location: jest.Mock;
            redirect: jest.Mock;
            render: jest.Mock;
            send: jest.Mock;
            sendFile: jest.Mock;
            sendStatus: jest.Mock;
            set: jest.Mock;
            status: jest.Mock;
            type: jest.Mock;
            vary: jest.Mock;
        }
    
        interface MockRequest extends Request {
            accepts: jest.Mock;
            acceptsCharsets: jest.Mock;
            acceptsEncodings: jest.Mock;
            acceptsLanguages: jest.Mock;
            get: jest.Mock;
            header: jest.Mock;
            is: jest.Mock;
            range: jest.Mock;
        }
    }

    global로 선언되었으므로 해당 타입을 사용 가능하다.

     

     

    res, req를 따로 인터페이스를 선언해서 사용하는 방법이 많이 나오던데, 해당 방법으로 하면 계속 타입이 옳지 않다고 에러를 뱉어서 jest-mock-req-res를 import했고, any타입을 없애기 위해서 인터페이스들을 추가로 선언해주었다. 

     

    다음에는 테이블을 mockup해서 라우터들을 테스트하는 포스트로 돌아오겠다.

    댓글

Designed by Tistory.