ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JEST를 이용한 테스트코드 작성
    JS 2024. 7. 1. 22:25

    Jest

    Jest는 자바스크립트 테스트 라이브러리이다. 다른 테스트 라이브러리는 여러 라이브러리들을 조합하여 사용하곤 했는데 Jest는 하나의 라이브러리로 Test Runner, Matcher 그리고 Mocking 까지 지원해준다.

     

    기본적인 사용법

    사용법을 알아보기 앞서 테스트할 코드를 먼저 보자

    /**
     * @description SMTP host 기반 domain 주소 추출 
     * @returns {String}
     */
    extractDomainFromHost(){
        if(this.host === MailTransporter.DEFAULT_HOST){
            throw new WrongArgsError(`Mail transporter 의 host를 입력해주세요`);
        }
    
        const pattern = /^smtp\.([^.]+)\.([^.]+)$/;
        const match = this.host.match(pattern);
        if (!match) {
            throw new WrongArgsError(`Mail transporter 형식이 올바르지 않습니다. ${this.host}`);
        }
    
        return `${match[1]}.${match[2]}`;
    }

    SMTP 호스트, 예를 들어 smtp.naver.com 같은 String 에서 naver.com 만 추출하는 함수이다.

    이 함수를 테스트 하는 테스트 코드를 보고 기본적인 사용법을 살펴보자.

    test('extractDomainFromHost 를 이용해 SMTP host 주소에서 메일 도메인 주소 추출', () =>{
        const transporter = new MailTransporterBuilder()
            .setHost(testHost)
            .setPort(testPort)
            .setUser(testUser)
            .setPass(testPass)
            .setSecure(testSecure)
            .setTls(testTls)
            .setTimeout(testTimeout)
            .build();
    
    
        expect(transporter.extractDomainFromHost()).toBe('test.com');
        transporter.host = 'smtp.test.net';
        expect(transporter.extractDomainFromHost()).toBe('test.net');
    
        transporter.host = MailTransporter.DEFAULT_HOST;
        expect(() => transporter.extractDomainFromHost()).toThrow(WrongArgsError);
    
        transporter.host = 'wrongSmtpHost';
        expect(() => transporter.extractDomainFromHost()).toThrow(WrongArgsError);
    })

    위의 extractDomainFromHost는 MailTransporter라는 Class에 작성되어있다. 처음 transporter는 Builder로 만들어주고 그 밑에 실제 테스트 코드가 시작된다.

     

    코드는 기본적으로 굉장히 직관적이다. expect() 함수를 실행하고 그 결과를 메소드 체이닝으로 toBe()를 통해 비교한다.

    expect(transporter.extractDomainFromHost()).toBe('test.com');

    위 코드는 함수 실행 결과가 'test.com' 과 일치해야 통과한다. 현재 갖고있는 host 주소는 smtp.test.com 으로 통과하게 된다.

    transporter.host = 'smtp.test.net';
    expect(transporter.extractDomainFromHost()).toBe('test.net');

    test.com 말고 .net 형식일때 정상 동작하는지 확인을 위해 host를 바꾸고 테스트 해보았다. 위 예시는 toBe뿐이지만 다른 검증 메소드들도 굉장히 많다. 기본적으로 메소드 이름이 굉장히 직관적이기에 보고 선택하기에 무리가 없다.

     

    transporter.host = MailTransporter.DEFAULT_HOST;
    expect(() => transporter.extractDomainFromHost()).toThrow(WrongArgsError);
    
    transporter.host = 'wrongSmtpHost';
    expect(() => transporter.extractDomainFromHost()).toThrow(WrongArgsError);

    이 코드는 에러를 처리하는 부분이다. expect() 내부에 콜백으로 테스트 할 함수를 넘긴다. 그리고 toThrow 라는 메소드로 내가 의도한 에러를 발생시키는지 확인할 수 있다. 나는 host가 기본값이거나 정규표현식과 비교하여 의도한 형식이 아니면 에러를 발생시키도록 처리했고 예상한대로 동작한다. 

     

    Mocking 테스트

    이번에도 테스트 코드를 작성할 함수를 먼저 보자

    /**
     * @description Send mail
     * @param {String} to 
     * @param {String} subject 
     * @param {String} text 
     * @returns 
     */
    send(to, subject, text){
        const mailOption = {
            from: this.getFromAddress(),
            to,
            subject,
            text
        }
        return new Promise((res, rej)=>{
            this.transporter.sendMail(mailOption, (err,info) =>{
                if(err){
                    rej(new BaseError(`Failed to sendMail ${err}`));
                }else{
                    res(true);
                }
            })
        })
    
    }

    메일을 보내는 send 함수이다. Promise를 return 해주고있다. 이 코드를 테스트하려면 생각해봐야 할 내용이 있다.

    Mock

    Mock 이란 가짜라는 의미이다. 내 send라는 함수는 nodemailer 라는 라이브러리를 이용해 메일을 보내는 함수이다.

    만약 테스트할때도 진짜 nodemailer 를 이용해서 테스트하면 어떻게 될까?

    1. 테스트를 할 때마다 정말 메일을 보낸다.

    2. 메일을 보낼 수 없는 주소로 설정해서 테스트하고 그에따라 발생하는 nodemailer 에러 코드를 처리한다.

    간단히 보기에도 위 두 방법 다 좋은 방법은 아닌것 같다. 이 문제를 해결하기 위해 mock 객체를 만드는 작업을 한다. 

    import nodemailer from 'nodemailer'
    jest.mock('nodemailer');

    내 테스트 코드 상단에 있는 코드이다. nodemailer를 import 해 와서 jest.mock() 이라는 함수에 넣어준다. 

    이 jest.mock() 이라는 함수를 이용하면 결과로 이 테스트 코드에서는 nodemailer에 대해서 가짜 객체들이 만들어진다. 

    예를들어 nodemailer.funcA() 라는 함수가 있다고 할때, jest로 mocking이 되어있다면 실제 nodemailer에서 정의한 함수가 아닌 가짜 함수가 호출된다. 이 가짜 함수는 내가 만들어 주기 나름이다. 

    테스트 코드

    test('nodemailer 를 이용해 실제 메일 발송 성공', () => {
    
        const mockSendMail = jest.fn().mockImplementation((mailOption, cb) =>{
            return cb(null, "thisIsCustomInfo");
        })
    
        nodemailer.createTransport.mockReturnValue({
            sendMail: mockSendMail,
        });
    
    
        const transporter = new MailTransporterBuilder()
            .setHost(testHost)
            .setPort(testPort)
            .setUser(testUser)
            .setPass(testPass)
            .setSecure(testSecure)
            .setTls(testTls)
            .setTimeout(testTimeout)
            .build();
    
    
        const testTo = "testTo@test.com"
        const testSub = "testSubject"
        const testBody = "testBody"
    
        const mailOption = {
            from: transporter.getFromAddress(),
            to : testTo,
            subject: testSub,
            text: testBody
        }
    
        expect(transporter.send(testTo, testSub, testBody)).resolves.toBe(true)
        expect(mockSendMail).toHaveBeenCalledTimes(1);
        expect(mockSendMail).toHaveBeenCalledWith(mailOption, expect.anything());
    
    })

    다시 돌아와서 내 send 함수를 테스트 하는 코드이다. 가장 먼저 나오는 부분이 위에서 설명한 내가 만들어주기 나름인 가짜 함수를 만들어 주는 부분이다. 

    const mockSendMail = jest.fn().mockImplementation((mailOption, cb) =>{
        return cb(null, "thisIsCustomInfo");
    })
    
    nodemailer.createTransport.mockReturnValue({
        sendMail: mockSendMail,
    });

    jest.fn()을 통해 가짜 함수를 만든다. 체인함수가 여러개 지원되는데 단순히 반환할 값을 지정해 줄 수도있고 내가한 mockImplementation()을 통해 직접 함수를 만들수 있다. 난 기존 nodemailer의 sendMail과 동일하게 mailOption과 callback 함수를 인자로 받는 함수를 만들어주었다. 내가 테스트하려는 send함수를 잘 살펴보면 알겠지만 callback함수의 첫번째 파라미터는 err 파라미터이다. 이걸 넘겨주면 err 가 발생했다고 처리한다. 지금은 성공 테스트이니 null을 넘겨준다. 

     

    nodemailer는 기본적으로 createTransport를 통해 transport라는것을 만들고 이 transport의 sendMail이라는 함수를 통해 메일을 보낸다. 나는 mockReturnValue 메소드를 이용해 nodemailer에서 createTransport함수를 실행하면 내가 만든 mockSendMail을 sendMail이라는 key로 갖고있는 가짜 객체를 리턴하도록 했다. 이럴경우 transport에서 sendMail을 호출하면 내가 만든 mock 함수가 호출된다. 물론 기존의 다른 nodemailer의 transport 기능들은 지원되지 않는다. 하지만 이 테스트에서는 sendMail만 필요하기에 의미가 없다. 

    expect(transporter.send(testTo, testSub, testBody)).resolves.toBe(true)

    내가 작성한 send 함수를 호출했을때 테스트이다. send함수는 Promise를 return한다. 이를 검증하기 위해 resolves를 이용하고 toBe 메소드를 이용해 내가 의도한 true 값이 나오는지 확인한다.

    expect(mockSendMail).toHaveBeenCalledTimes(1);

    mocking 한 함수는 특수한 기능들도 가능하다. Jest에서 이 함수가 몇번 호출되었는지 알고 있다는 것이다. toHaveBeenCalledTimes를 이용해 내 가짜 함수가 1번 호출됐는지 확인해본다. 

    const mailOption = {
        from: transporter.getFromAddress(),
        to : testTo,
        subject: testSub,
        text: testBody
    }
    expect(mockSendMail).toHaveBeenCalledWith(mailOption, expect.anything());

    mocking 한 함수의 또 다른 기능이다. 자신이 어떤 파라미터로 호출이 됐는지 알 수 있다. 내가 만든 mailOption으로 호출이 됐는지 확인한다. mockSendMail은 위에 보면 알겠지만 mailOption과 콜백 함수로 호출된다. toHaveBeenCalledWith는 이 두가지 파라미터가 모두 맞아야 true이다. 하지만 이 넘기는 callback 함수는 내가 테스트용으로 정의한 것으로 사실 아무 의미가 없다. 이때는 expect.anything() 이라는 기능으로, 어떤것이 넘어와도 상관없다 라는 의미를 적는다. 

    const mockSendMail = jest.fn().mockImplementation((mailOption, cb) =>{
        return cb("Test Error", null);
    })

    간단하지만 발송 실패 테스트 케이스에서의 가짜 mockSendMail 함수이다. 성공때와는 다르게 콜백 함수 호출시 첫번째 파라미터인 error 값을 null이 아닌 값으로 넘겨주고있다. 

    정리

    Jest를 이용해 javascript 테스트 코드를 작성해보았다. 기타 다른 기능들은 출처에 남기는 글에서 확인해 볼 수 있다.

    하지만 사용하는 메소드명이 굉장히 직관적이라 큰 무리없이 사용이 가능했다.

     

    테스트 코드를 작성함에따라 생각해야할 점들이 많았다. 간단할줄 알았지만 테스트코드가 의미가 있는 코드가 되기 위해선 기존에 작성했던 코드들을 테스트 가능하도록 리팩토링을 해야했다. 

    출처

    https://www.daleseo.com/jest-mock-modules/

     

    Jest의 jest.mock()을 이용한 모듈 모킹

    Engineering Blog by Dale Seo

    www.daleseo.com

    https://inpa.tistory.com/entry/JEST-%F0%9F%93%9A-%EB%AA%A8%ED%82%B9-mocking-jestfn-jestspyOn

     

    [JEST] 📚 모킹 Mocking 정리 - jest.fn / jest.mock /jest.spyOn

    Mocking 원리 mocking이란 (mock = 모조품) 뜻 그대로 받아드리면 된다. 즉 테스트하고자 하는 코드가 의존하는 function이나 class에 대해 모조품을 만들어 '일단' 돌아가게 하는 것이다. 한마디로, 단위

    inpa.tistory.com

    https://inpa.tistory.com/entry/JEST-%F0%9F%93%9A-jest-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%A6%AC

     

    [JEST] 📚 JEST 소개 & 기본 사용법 정리

    JEST 란? Jest는 페이스북에서 만들어서 React와 더불어 많은 자바스크립트 개발자들로 부터 좋은 반응을 얻고 있는 테스팅 라이브러리다. 출시 초기에는 프론트앤드에서 주로 쓰였지만 최근에는

    inpa.tistory.com

     

Designed by Tistory.