Jest | 목 함수(Mock Functions)

Kate Jung·2022년 4월 7일
1

Front-end Test

목록 보기
5/17
post-thumbnail

📌 목 함수(Mock Functions) 란?

테스트 하기 위해 흉내만 내는 함수

즉, 실제 함수를 구현한 것이 아니라 그저 흉내만 낸, 테스트 하기 위해서 만든 일종의 모형


  • mock 의 사전적 의미

    모의의, 가짜의

  • mock up 의 사전적 의미

    모형, 모의

📌 기본 사용법 / mock / mock.calls

🔹 필요한 경우

어떤 작업을 테스트 할 때, 목 함수로 간단히 테스트 하는 것이 실제로 구현하는 것보다 나을 때

  • 예시

    user DB에 접근해서 user list를 select해오는 작업할 때, 작성해야 할 코드가 너무 많은 경우

    • 만들어져 있는 코드가 있다면

      → 상관 無

    • but, 새로 만든다면

      • 테스트를 위한 코드보다 더 길어질 수도.
      • 이런 작업은 외부 요인의 영향을 받음(네트워크 환경, DB의 상태 등) 테스트에서 같은 코드는 동일한 결과를 내는 것이 중요.

🔹 기본 사용법

  • jest.fn()으로 제작

  • 호출 및 인수 넘기기 가능

  • mock 프로퍼티

    호출 되었던 값들이 저장됨

    • 내부

      • calls 배열

        함수 호출된 횟수, 호출될 때 전달된 인수 파악 가능

▪︎ 예시 1. 기본

  • fn.test.js

    const mockFn = jest.fn(); // ✅ jest.fn()으로 목 함수 제작
    
    // ✅ 호출
    mockFn();
    mockFn(1); // ✅ 인수 넘기기
    
    // 의미 없는 테스트 작성
    test("dd", () => {
    	console.log(mockFn.mock.calls) // 👈 목 함수에 'mock' 프로퍼티가 있고 그 안에 있는 calls
      expect("dd").toBe("dd");
    });
  • 테스트 결과

    > jest@1.0.0 test
    > jest
    
      console.log
        [ [], [ 1 ] ]  
        # 👆 배열 내부에 전달된 인수의 값이 배열로 존재.
    
          at Object.<anonymous> (fn.test.js:7:11)
    
     PASS  ./fn.test.js
      ✓ dd (10 ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        0.124 s, estimated 1 s

▪︎ 예시 2. mock.calls 활용

  • fn.test.js

    (...생략)
    
    test("함수는 2번 호출됩니다.", () => {
      expect(mockFn.mock.calls.length).toBe(2); // 👈 calls.length는 호출된 횟수
    });
    test("2번째로 호출된 함수의 첫 번째 인수는 1입니다.", () => {
      expect(mockFn.mock.calls[1][0]).toBe(1);
    });
  • 테스트 결과

    모두 통과

▪︎ 예시 3. 콜백 함수 대체

콜백 함수를 새로 만들지 않아도 목 함수를 이용해서 해당 함수가 잘 동작 하는지 파악 가능

  • 배열(숫자)을 반복하면서 +1 한 값을 콜백 함수에 전달해주는 forEachAdd1 함수

    • fn.test.js

      function forEachAdd1(arr) {
        arr.forEach((num) => {
          // fn(num+1)       👈 여기에 들어갈 함수는 아직 제작 안함.
        });
      }

      빠르고 간단한 테스트를 위해 목 함수 활용

      const mockFn = jest.fn(); // 👈 목 함수를 만들고
      
      function forEachAdd1(arr) {
        arr.forEach((num) => {
          mockFn(num + 1); // 👈 콜백 함수로 전달
        });
      }
      
      // 👇 실제 사용
      forEachAdd1([10, 20, 30]); 
      
      test("함수 호출은 3번 됩니다.", () => {
        expect(mockFn.mock.calls.length).toBe(3);
      });
      test("전달된 값은 11, 21, 31입니다.", () => { // 전달된 값은 1씩 증가한 값
        expect(mockFn.mock.calls[0][0]).toBe(11);
        expect(mockFn.mock.calls[1][0]).toBe(21);
        expect(mockFn.mock.calls[2][0]).toBe(31);
      });
    • 테스트 결과

      모두 통과

📌 mock.results

🔹 예시

어떤 값을 리턴(숫자 받아서 +1)하는 함수 제작

▪︎ 사용법 (fn.test.js)

const mockFn = jest.fn((num) => num + 1); 
// 1. 숫자를 받아 +1
// 2. jest.fn의 첫 번째 인수로 함수 전달
// 3. 호출

mockFn(10);
mockFn(20);
mockFn(30);

// 3번 호출
test("함수 호출은 3번 됩니다.", () => {
  console.log(mockFn.mock.results); // 👈 mock.results 확인할 것
  expect(mockFn.mock.calls.length).toBe(3);
});

▪︎ 테스트 결과

results에는 return 된 값이 배열로 들어옴.

seonmijung@Galaxy-A7 Jest % npm test

> jest@1.0.0 test
> jest
	
  console.log
    [
      { type: 'return', value: 11 }, # 👈 1씩 증가된 값이 return
      { type: 'return', value: 21 },
      { type: 'return', value: 31 }
    ]

      at Object.<anonymous> (fn.test.js:8:11)

 PASS  ./fn.test.js
  ✓ 함수 호출은 3번 됩니다. (11 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.125 s, estimated 1 s
Ran all test suites.

▪︎ 활용 (fn.test.js)

const mockFn = jest.fn((num) => num + 1);

mockFn(10);
mockFn(20);
mockFn(30);

test("10에서 1 증가한 값이 반환된다.", () => {
  expect(mockFn.mock.results[0].value).toBe(11);
});
test("20에서 1 증가한 값이 반환된다.", () => {
  expect(mockFn.mock.results[1].value).toBe(21);
});
test("30에서 1 증가한 값이 반환된다.", () => {
  expect(mockFn.mock.results[2].value).toBe(31);
});

1번째 실행된 것은 results[0]에 들어갈 거고 2 번째, 3 번째 호출한 것은 1과 2에 들어감.

테스트는 모두 통과.

📌 .mockReturnValue

실행할 때 마다 각각 다른 값을 return 가능하게 함.

🔹 사용법

  • fn.test.js
    const mockFn = jest.fn();
    
    mockFn
      .mockReturnValueOnce(10) // 👈 중간에 return 값을 바꾸려면 once 붙임
      .mockReturnValueOnce(20)
      .mockReturnValueOnce(30)
      .mockReturnValue(40); // 👈 마지막에는 once 뺌.
    
    mockFn();
    mockFn();
    mockFn();
    mockFn();
    
    test("dd", () => {
      console.log(mockFn.mock.results); // 👈 확인
      expect("dd").toBe("dd");
    });
  • 테스트 결과
    seonmijung@Galaxy-A7 Jest % npm test
    
    > jest@1.0.0 test
    > jest
    
      console.log
        [
          { type: 'return', value: 10 }, # 👈 10, 20, 30, 40이 리턴 된 것 확인
          { type: 'return', value: 20 },
          { type: 'return', value: 30 },
          { type: 'return', value: 40 }
        ]
    
          at Object.<anonymous> (fn.test.js:15:11)
    
     PASS  ./fn.test.js
      ✓ dd (11 ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        0.129 s, estimated 1 s
    Ran all test suites.

🔹 예시

1 ~ 5까지 받아서 홀수만 리턴하는 함수

  • fn.test.js
    1. 콜백 함수 작성

      const mockFn = jest.fn();
      
      [1, 2, 3, 4, 5].filter((num) => callback(num)); // 👈 callback
    2. callback 함수가 홀/짝을 판별해주는 역할을 해야 되는데 지금 당장 코드를 만들 형편이 되지 않는다면 → 목 함수를 사용

      const mockFn = jest.fn();
      
      [1, 2, 3, 4, 5].filter((num) => mockFn(num)); // 👈 이렇게 사용
    3. 목 함수는 truefalse를 번갈아 가면서 리턴

      const mockFn = jest.fn();
      
      mockFn
        .mockReturnValueOnce(true)
        .mockReturnValueOnce(false)
        .mockReturnValueOnce(true)
        .mockReturnValueOnce(false)
        .mockReturnValue(true);
      
      let result = [1, 2, 3, 4, 5].filter((num) => mockFn(num)); // 👈 mockFn 부분 💥
      
      test("홀수는 1, 3, 5", () => {
        expect(result).toStrictEqual([1, 3, 5]); // 배열을 확인할 때는 toStrictEqual(O), toBe(X)
      });
      • 원래는 위 mockFn 부분 💥에 홀짝 판별이 가능한 함수를 작성해야하는데 일단 목 함수를 사용하고 어떤 숫자가 넘어 오던지 순서대로 true, false를 번갈아 가면서 return을 해주고 있음.

      • 실행을 해보면 1, 3, 5만 (num) => mockFn(num) 부분이 true일 것

        → 그래서 result는 1, 3, 5

  • 테스트 결과

    통과

    이제 목 함수 대신 실제 코드를 작성하면 됨.

📌 .mockResolvedValue

비동기 함수 처럼 사용 가능
(
then을 이용해서 .mockResolvedValue(); 에서 return해주는 값을 비교 가능)

  • 예시

    • fn.test.js
      const mockFn = jest.fn();
      
      mockFn.mockResolvedValue({ name: "Mike" }); // 👈 
      
      test("받아온 이름은 Mike", () => {
        mockFn().then((res) => { // 👈 then 이용
          expect(res.name).toBe("Mike");
        });
      });
    • 테스트 결과
      • 통과
      • then을 이용해서 mockFn.mockResolvedValue({ name: "Mike" }); 에서 return해주는 값을 비교

📌 mocking module

🔹 사용법

  • jest.mock() 을 사용해서 mocking module 로 만듦

  • mockReturnValue() 등으로 return할 값 설정 가능

  • 효과

    실제 코드는 실행되지 않고 목 함수가 동작 (설정한 리턴 값을 반환)

🔹 예시 | 외부 코드를 활용한 테스트

외부 코드(fn.js)를 활용한 테스트가 필요하다고 가정

user를 생성하는 함수를 테스트 해보고 싶은데 테스트 할 때마다 user가 생겨버리면 곤란. 그렇다고 테스트가 끝날 때마다 롤백해주는 것도 번거로움.

→ 이럴 때 mocking module 사용


▪︎ 실제 코드 사용할 경우

번거로움 (테스트 할 때마다 user가 생겨서 곤란. 끝날 때마다 롤백 필요.)

  • 외부 코드 (fn.js)

    이름을 받아서 user객체를 리턴해주는 함수 제작

    실제로는 userDB에 user를 생성해주는 함수라고 가정

    const fn = {
      (...생략)
    
      createUser: (name) => {  // 👈
        console.log("실제로 사용자가 생성 되었습니다.");
        return {
          name,
        };
      },
    
      (...생략)
    };
    
    module.exports = fn;
  • 외부 코드 사용 (fn.test.js)

    const fn = require("./fn");
    
    test("유저를 만든다", () => {
      const user = fn.createUser("Mike");
      expect(user.name).toBe("Mike");
    });
  • 테스트 결과

    통과. → 이런 경우 다시 DB로 접속해서 방금 만든 test user는 삭제 필요.

    seonmijung@Galaxy-A7 Jest % npm test
    
    > jest@1.0.0 test
    > jest
    
      console.log
        실제로 사용자가 생성 되었습니다. # 👈 실제로 user가 생겨버림
    
          at Object.createUser (fn.js:4:13)
    
     PASS  ./fn.test.js
      ✓ 유저를 만든다 (10 ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        0.133 s, estimated 1 s
    Ran all test suites.

▪︎ mocking module 사용

  • fn.test.js

    const fn = require("./fn");
    
    jest.mock("./fn") // 👈 jest.mock()으로 fn을 mocking module로 만듦
    fn.createUser.mockReturnValue({ name: "Mike" }); 
    /* 👆 
    fn의 createUser를 사용할텐데 mockReturnValue를 통해서 
    { name: "Mike" } 객체를 return */
    
    test("유저를 만든다", () => {
      (...생략)
    });
    • 실제 fn.createUser 는 호출 x.

    • { name: "Mike" }를 반환해주는 목 함수가 동작

  • 테스트 결과

    • 통과

    • 아까 보였던 로그(실제로 사용자가 생성 되었습니다.)는 안 찍힘. → 실제 user 생성 안됨.

📌 유용한 matchers

🔸 toBeCalled

한 번이라도 호출 됬으면 통과

🔸 toBeCalledTimes

정확한 호출 횟수

🔸 toBeCalledWith

인수로 어떤 값을 받았는지 체크

🔸 lastCalledWith

마지막으로 실행된 인수만 체크

🔹 예시

  • fn.test.js

    const mockFn = jest.fn();
    
    mockFn(10, 20);
    mockFn();
    mockFn(30, 40);
    
    test("한 번 이상 호출?", () => {
      expect(mockFn).toBeCalled();
    });
    test("정확히 3번 호출?", () => {
      expect(mockFn).toBeCalledTimes(3); // 3번 호출되었기 때문에 → 3
    });
    test("10과 20을 전달 받은 함수가 있는가?", () => {
      expect(mockFn).toBeCalledWith(10, 20);
    });
    test("마지막 함수는 30과 40을 받았음?", () => {
      expect(mockFn).lastCalledWith(30, 40);
    });
  • 테스트 결과

    모두 통과

    • toBeCalledWith 의 인수를 30, 40으로 바꾸어도 → 통과

    • lastCalledWith 의 인수를 10, 20으로 바꾸면 → 실패.

      마지막 mockFn은 30, 40을 전달했기 때문


참고

  • 코딩앙마 강좌_Jest
profile
복습 목적 블로그 입니다.

0개의 댓글