본문 바로가기
JavaScript

Node.js 카카오 로그인 유닛테스트 Unit Test (mockReturnValue)

by LeeJ1Hyun 2022. 12. 31.

* 2022년 9월 16일 velog에 작성했던 게시글을 옮겨온 글입니다.

 

직접 만든 API가 아닌 외부 API를 이용하는 API를 테스트 하려면 외부 API를 목킹(mocking)해야 한다. 프로젝트 수준에서 일어날 수 있는 대표적인 상황을 예시로 들자면 소셜 로그인이다. 카카오, 구글 등 외부 API를 사용하여 정보를 얻어오고 해당 정보를 가공하여 GET, POST, DELETE, PUT, PATCH 등 API를 만들면 유닛 테스트를 작성할 때도 외부 API를 거쳐야만 테스트가 가능하다.

 

// 카카오 로그인 API (DB 저장)

const authDao = require("../models/authDao");
const jwt = require("jsonwebtoken");
const axios = require("axios");
const error = require("../middlewares/errorConstructor");

const signInWithKakao = async (kakaoToken) => {
  const result = await axios.get("https://kapi.kakao.com/v2/user/me", {
    headers: {
      Authorization: `Bearer ${kakaoToken}`,
    },
  });

  const nickname = result.data.kakao_account.profile.nickname;
  const email = result.data.kakao_account.email;
  const kakaoId = result.data.id;

  if (!nickname || !email || !kakaoId) throw new error("KEY_ERROR", 400);

  const user = await authDao.getUserByEmail(email);

  if (!user) {
    await authDao.signUp(nickname, email, kakaoId);
  }

  return jwt.sign({ sub: kakaoId }, process.env.JWT_SECRET);
};

module.exports = { signInWithKakao };

 

위 API 로직을 살펴보면,
signInWithKakao API POST 요청 => Kakao API(axios) GET 요청 => 얻어낸 사용자 정보를 변수에 할당 => 데이터베이스에 INSERT 순으로 이어진다. 결국 데이터 베이스에 넣으려고 하는 사용자 정보를 얻기 위해선 외부 API를 무조건 거쳐야 한다. 유닛 테스트를 할 때도 INSERT 하기 위한 정보들이 필요하기 때문에 이는 필수조건이다. 하지만 우리가 테스트하고 싶은 건 내가 만든 회원등록 API가 제대러로 작동하느냐이지 카카오 로그인 API를 테스트하려는 건 아니다.

 

이럴 때 사용할 수 있는 함수가 바로 jest의 mockFn.mockReturnValue(value)이다. 항상 함수명을 보면 해당 함수가 어떤 역할을 하는지 알 수 있다. mock + ReturnValue(value) 말 그대로 리턴값을 목킹하는 함수이다. 가짜로 만들어진 이 반환값을 이용하여 테스트하면 된다.

 

mockFn.mockReturnValue(value) 사용법

const mock = jest.fn();

mock.mockReturnValue(42);
mock(); // 42

mock.mockReturnValue(43);
mock(); // 43

 

mock이라는 함수를 선언하고 jest.fn() 함수로 가짜 함수를 하나 만들어준다. 하지만 그냥 mock()을 호출하면 undifined가 출력된다. 리턴값을 지정해주지 않았으니 당연하다. 모조 함수의 리턴값을 지정해주는 것이 바로 mockReturnValue이다.

 

mock.mockReturnValue(반환값)을 해주고 mock()을 호출하면 지정한 반환값이 출력된다. 원리는 정말 간단하지만 실제 API에 적용하려고 하니 어떤 값을 mocking 해줘야 하는지 감이 잘 오지 않았다.

 

카카오 로그인 API 사용시 사용자 정보를 받아오는 요청을 보내면 다음과 같은 형태의 응답을 보내준다.

 

{
        id: 1010101022,
        connected_at: "2022-08-30T14:41:02Z",
        properties: {
          nickname: "아무개",
        },
        kakao_account: {
          profile_nickname_needs_agreement: false,
          profile: {
            nickname: "아무개",
          },
          has_email: true,
          email_needs_agreement: false,
          is_email_valid: true,
          is_email_verified: true,
          email: "abc@gmail.com",
        }

 

나의 동의 항목은 닉네임과 이메일이라 본인이 한 애플리케이션 설정에 따라 형태는 조금 다를 수도 있다. axios 요청을 보내 얻어왔기 때문에 이 데이터는 result.data로 접근해야 이러한 형태로 출력된다. 값을 리턴해주는데 있어 fetch와 약간 차이점이 있다. 이제 이 값을 mock하면 내가 만든 API를 테스트 할 수 있다.

 

카카오 로그인 API 유닛 테스트

const request = require("supertest");
const axios = require("axios");
const { createApp } = require("../app");
const { AppDataSource } = require("../models/dataSource");

describe("kakao SignIn", () => {
  let app;

  beforeAll(async () => {
    app = createApp();
    await AppDataSource.initialize();
  });

  afterAll(async () => {
    await AppDataSource.query(`SET FOREIGN_KEY_CHECKS = 0`);
    await AppDataSource.query(`TRUNCATE users`);
    await AppDataSource.query(`SET FOREIGN_KEY_CHECKS = 1`);
    await AppDataSource.destroy();
  });

  test("SUCCESS: kakao signin", async () => {
    axios.get = jest.fn().mockReturnValue({ //카카오 API에 모조 결과값을 넣어준다.
      data: { // 닉네임, 이메일 등에 접근하기 위해서 data라는 key 값을 지정해줘야 한다.
        id: 1010101022,
        connected_at: "2022-08-30T14:41:02Z",
        properties: {
          nickname: "아무개",
        },
        kakao_account: {
          profile_nickname_needs_agreement: false,
          profile: {
            nickname: "아무개",
          },
          has_email: true,
          email_needs_agreement: false,
          is_email_valid: true,
          is_email_verified: true,
          email: "abc@gmail.com",
        },
      },
    });

    await request(app)
      .post("/auth/signIn")
      .set({
        Authorization: "Bearer accessToken", //실제 유효하지 않은 토큰이어도 상관없다. 어차피 리턴값을 목킹했기 때문에 실제 유저의 토큰이 필요없기 때문이다.
      })
      .expect(200);
  });

  test("FAILED: kakaoToken not exist", async () => {
    axios.get = jest.fn().mockReturnValue({
      data: {
        id: 1010101022,
        connected_at: "2022-08-30T14:41:02Z",
        properties: {
          nickname: "아무개",
        },
        kakao_account: {
          profile_nickname_needs_agreement: false,
          profile: {
            nickname: "아무개",
          },
          has_email: true,
          email_needs_agreement: false,
          is_email_valid: true,
          is_email_verified: true,
          email: "abc@gmail.com",
        },
      },
    });

    await request(app).
    post("/auth/signIn")
    .expect(400);
  });
});

 

유닛 테스트 결과

'JavaScript' 카테고리의 다른 글

Node.js 카카오 로그인 및 DB INSERT 총정리  (0) 2022.12.31
[]===[], {}==={} 는 거짓(false)이다  (0) 2022.12.31
if문과 true&false  (0) 2022.12.31
배열의 typeof는 Object이다  (0) 2022.12.31

댓글