[NODEJS] - jest

jsbak·2022년 3월 17일
1
post-thumbnail

단위 테스트 ( Unit )

[TDD] 단위 테스트와 TDD(테스트 주도 개발) 프로그래밍 방법 소개 - (1/5)

단위 테스트(Unit Test)를 작성해야 하는 이유

  • 코드를 수정하거나 기능을 추가할 때 수시로 빠르게 검증 할 수 있다.
  • 리팩토링 시에 안정성을 확보
  • 개발 및 테스팅에 대한 시간과 비용을 절감

테스트 코드 작성 시 유의사항

  • 1개의 테스트 함수는 1개의 개념만을 테스트
    ex. 중복으로 등록을 실패하는 경우, 성공하는 경우일 때 → 2개의 테스트 메소드로 작성
  • 1개의 테스트 함수에 대해 Assert(): Java -> expect(): jest 를 최소화
  • 테스트 함수 작성 시 test 메서드 명을 구체적으로 표시할 것
  • 테스트 케이스의 이름을 쉽게 알아 볼 수 있도록 표시

FIRST 규칙

  • Fast : 빠르게 동작하여 자주 돌릴 수 있도록 한다.
  • Independent : 각각의 테스트는 독립적이며 서로 의존해서는 안된다.
  • Repeatable : 어느 환경에서도 반복 가능해야 한다.
  • Self-Validating : 테스트는 성공 or 실패로 bool 값으로 결과를 내어 자체적으로 검증되도록 한다.
  • Timely : 테스트하려는 실제 코드를 구현하기 직전에 구현해야 한다.

참고한 블로그 글

개발이 끝난 뒤에 문제가 없는지 확인하기 위해 애플리케이션을 실행하고, 직접 수동 (통합) 테스트를 진행해야 한다. 단위 테스트를 작성하지 않은 코드들은 테스트를 작성하지 않은 코드들 보다 버그가 잠재되어 있을 확률이 높은데, 문제는 직접 테스트 하는 비용이 너무 크다는 것이다. 그 이유는 통합 테스트를 위해서는 캐시, 데이터베이스 등 외부 컴포넌트들과 연결 등 부가적인 시간이 필요하기 때문이다.

테스트 코드를 작성하지 않았다면 여러 개의 버그가 잠재되어 있을 확률이 있고, 모든 버그들을 수정하고 테스트를 반복하는 비용은 기하급수적으로 늘어나게 된다. 그러므로 우리는 개발 및 테스팅에 대한 비용을 줄이기 위해 단위 테스트를 작성해야 한다.

jest

jestjs.io

  • npm instal -D jest : 테스팅 툴은 개발 시에만 사용하기 때문에 -D 옵션으로 설치
  • package.json 에 test라는 명령어를 등록
  • 필요한 부분은 jestjs 공식 문서를 참고하는 것이 좋음.
  • npm test, npm test testfilename
    • npm test example.test.js
{
  "name": "fsmarket-backend",
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "start": "pm2 start ecosystem.config.json",
    "test": "jest",
    "dev": "PORT=5001 nodemon server.js"
  },
  ...
}
  • 테스트용 파일은 파일명과 확장자 사이에 test나 spec을 넣으면 된다.
  • npm test로 테스트 코드를 실행할 수 있으며 test 가 들어 모든 코드를 찾아서 실행

참조 링크

Mock Functions Jest

The Jest Object Jest

[JEST] 모킹 Mocking 정리 (jest.fn / jest.mock /jest.spyOn)

[Jest] jest.mock() 모듈 모킹

[Jest]jest.fn(), jest.spyOn() 함수 모킹

test()

test(name, fn, timeout), it(name, fn, timeout)

test('did not rain', () => {
  expect(inchesOfRain()).toBe(0);
});
  • 첫 번째 인수 : 테스트 이름
  • 두 번째 인수 : 테스트할 기대치를 포함하는 함수
  • 세 번째 인수(선택 사항) : timeout(밀리초 단위, Default - 5초), 테스트 중단 처리하기 전에 대기할 시간으호 테스트가 초과해서는 안되는 시간

참고: 테스트에서 promise가 반환 되면 Jest는 promise가 해결될 때까지 대기한 다음에 테스트를 완료합니다.

Jest는 테스트 함수에 done 이라고 callback 함수 인수를 제공하면 대기합니다.

예를 들어, fetchBeverageList() lemon 이 포함된 목록으로 확인되어야 하는 약속을 반환한다고 가정해 보겠습니다.

test('has lemon in it', () => {
  return fetchBeverageList().then(list => {
    expect(list).toContain('lemon');
  });
});

test에 대한 호출 이 즉시 반환 되더라도 Promise가 해결될 때까지 테스트 완료를 보류

Expect

jest.js - expect()

  • expect(value) : value 는 실제 코드
  • .toBe(value) : 기본 값을 비교하거나 개체 인스턴스의 참조 ID를 확인하는 데 사용합니다.
  • .toEqual(value) : 객체 인스턴스의 모든 속성을 재귀적으로 비교하는 데 사용
    • === 엄격한 동등 연산자 보다 테스트에 더 나은 Object.is 원시 값을 비교하도록 호출 합니다.
    • 값이 같은 지만 비교
const can1 = {
  flavor: 'grapefruit',
  ounces: 12,
};
const can2 = {
  flavor: 'grapefruit',
  ounces: 12,
};

describe('the La Croix cans on my desk', () => {
  test('have all the same properties', () => {
    expect(can1).toEqual(can2);
  });
  test('are not the exact same can', () => {
    expect(can1).not.toBe(can2);
  });
});

describe

jest.js - describe()

describe(name, fn): 여러 관련 테스트를 그룹화하는 블록 생성

  • 첫 번째 인수: 그룹에 대한 설명
  • 두 번째 인수: 함수는 그룹에 대한 내용
const binaryStringToNumber = binString => {
  if (!/^[01]+$/.test(binString)) {
    throw new CustomError('Not a binary number.');
  }

  return parseInt(binString, 2);
};

describe('binaryStringToNumber', () => {
  describe('given an invalid binary string', () => {
    test('composed of non-numbers throws CustomError', () => {
      expect(() => binaryStringToNumber('abc')).toThrowError(CustomError);
    });

    test('with extra whitespace throws CustomError', () => {
      expect(() => binaryStringToNumber('  100')).toThrowError(CustomError);
    });
  });

  describe('given a valid binary string', () => {
    test('returns the correct number', () => {
      expect(binaryStringToNumber('100')).toBe(4);
    });
  });
});
  • test 필수 사항은 아님.
  • describe 중첩 가능

Testing Asynchronous Code

jest.js - 비동기 코드 테스트

  • 비동기 테스트는 test() 에 전달된 함수 앞에 async 키워드를 사용
test('the data is peanut butter', async () => {
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});
  • 작성해본 코드
test('조회가 되는 경우', async () => {
  // given
  const address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A300b00';
  const blockchainId = 1;
    
  // when
  const wallet = await walletService.findByAddressAndBlockchainId(address, blockchainId);

  // then
  expect(wallet.address).toEqual('0xFDEa65C8e26263F6d9A1B5de9555D2931A300b00');
  expect(wallet.blockchainId).toEqual(1);
}

Matchers

  • toBe(object) : 같은 객체인지 비교
  • toEqual(object) : 같은 값인지 비교
  • not : 원래 기능 반대 not.toBe(...)

테스트에서는 undefined, null, false를 구별할 필요가 있는 경우 사용

  • toBeNull : null인지만 확인
  • toBeUndefiend
  • toBeDefined : toBeUndefiend 의 반대
  • toBeTruthy : if 문이 참인가
  • toBeFalsy : if 문이 거짓인가

숫자 비교

  • toBeGreaterThan : ~보다 큼
  • toBeGreaterThanOrEqual : ~보다 크거나 같음
  • toBeLessThan : ~보다 작음
  • toBeLessThanOrEqual : ~보다 작거나 같음
test('two plus two', () => {
  const value = 2 + 2;
  expect(value).toBeGreaterThan(3);
  expect(value).toBeGreaterThanOrEqual(3.5);
  expect(value).toBeLessThan(5);
  expect(value).toBeLessThanOrEqual(4.5);

  // toBe and toEqual are equivalent for numbers
  expect(value).toBe(4);
  expect(value).toEqual(4);
});

소수를 다룰 때는 toEqual 는 반올림을 하기 때문에 대신 toBeCloseTo 를 사용

test('adding floating point numbers', () => {
  const value = 0.1 + 0.2;
  //expect(value).toBe(0.3);           This won't work because of rounding error
  expect(value).toBeCloseTo(0.3); // This works.
});
  • toMatch : 문자열에 대한 정규식표현을 확인
test('there is no I in team', () => {
  expect('team').not.toMatch(/I/);
});

test('but there is a "stop" in Christoph', () => {
  expect('Christoph').toMatch(/stop/);
});
  • toContain : 배열이나 iterable 에 값이 있는지 확인 할때.
const shoppingList = [
  'diapers',
  'kleenex',
  'trash bags',
  'paper towels',
  'milk',
];

test('the shopping list has milk on it', () => {
  expect(shoppingList).toContain('milk');
  expect(new Set(shoppingList)).toContain('milk');
});
  • toThrow : 특정 함수가 호출될 때 오류가 발생하는지 여부를 테스트
    • Note: the function that throws an exception needs to be invoked within a wrapping function otherwise the toThrow assertion will fail.
function compileAndroidCode() {
  throw new Error('you are using the wrong JDK');
}

test('compiling android goes as expected', () => {
  expect(() => compileAndroidCode()).toThrow();
  expect(() => compileAndroidCode()).toThrow(Error);

  // You can also use the exact error message or a regexp
  expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
  expect(() => compileAndroidCode()).toThrow(/JDK/);
});

after/beforeAll(fn, timeout) / after/beforeEach

afterAll(fn, timeout)
beforeAll(fn, timeout)

  • 테스트가 실행되기 전 실행
  • 선택적으로 timeout (밀리초 단위) 설정 가능, Default 값은 5초
  • DBConnection 과 같은 많은 테스트에서 사용할 전역 상태를 설정하려는 경우에 유용
  • xxxEach 는 하나씩 처리할 때

Mocking

jest.js - 모의 함수
[JEST] 📚 모킹 Mocking 정리 (jest.fn / jest.mock /jest.spyOn)
[번역] Jest Mocks에 대한 이해

  • 모의 객체(Mock Object)란?
    주로 객체 지향 프로그래밍으로 개발한 프로그램을 테스트 할 경우 테스트를 수행할 모듈과 연결되는 외부의 다른 서비스나 모듈들을 실제 사용하는 모듈을 사용하지 않고 실제의 모듈을 "흉내"내는 "가짜" 모듈을 작성하여 테스트의 효용성을 높이는데 사용하는 객체이다. 사용자 인터페이스(UI)나 데이터베이스 테스트 등과 같이 자동화된 테스트를 수행하기 어려운 때 널리 사용된다. - Wiki - 모의 객체

실제 데이터 베이스 접근과 외부 API 연동을 해야하는 부분은 직접 가져다가 붙이는 경우 DB 설치 / 데이터 존재 / DB Connection 등등에서 발생하는 문제가 해당 기능의 문제인지 DB의 문제인지도 알기 어려우며, 오작동 / 비용이 발생하는 외부 API 연동시 테스트를 진행하기 어렵고 실질적인 기능 단위의 테스트를 수행하기 어렵다.

따라서 DB 나 외부 API를 통한 데이터를 정상적으로 받아온다고 가정하고 하고 테스트 해야한다.

모듈과 함수를 Mocking

  • Jest에서 모듈과 함수를 Mocking 하는 3가지 방법
    • jest.fn: Mock a function
    • jest.mock: Mock a module
    • jest.spyOn: Spy or mock a function

Mocking modules

Mocking modules
모듈 모의 우회

Mock Functions

  • 사용되지 않은 새로운 모의 함수 반환
  • 마음대로 모의 구현
const mockFn = jest.fn();
mockFn();
expect(mockFn).toHaveBeenCalled();

// With a mock implementation:
const returnsTrue = jest.fn(() => true);
console.log(returnsTrue()); // true;

Mock Object

  • mock property
  • mock: [Getter/Setter], mock property는 호출한 내역에 대해서 기억하고 있다.
Mocking 한 것에 대한 내용
jest.fn() 해서  Mock 함수를 생성했을 때 담기는 것: [Function: mockConstructor] {
    _isMockFunction: true,
    getMockImplementation: [Function (anonymous)],
    mock: [Getter/Setter],
    mockClear: [Function (anonymous)],
    mockReset: [Function (anonymous)],
    mockRestore: [Function (anonymous)],
    mockReturnValueOnce: [Function (anonymous)],
    mockResolvedValueOnce: [Function (anonymous)],
    mockRejectedValueOnce: [Function (anonymous)],
    mockReturnValue: [Function (anonymous)],
    mockResolvedValue: [Function (anonymous)],
    mockRejectedValue: [Function (anonymous)],
    mockImplementationOnce: [Function (anonymous)],
    mockImplementation: [Function (anonymous)],
    mockReturnThis: [Function (anonymous)],
    mockName: [Function (anonymous)],
    getMockName: [Function (anonymous)]
}

mock.calls

mock property 에는 calls 라 불리는 배열이 존재하는데
mock 함수가 불릴 때마다의 인수 정보 등이 담겨 있다.

  • calls 의 length = mock Function 이 호출된 횟수
{
	calls: [ [], [ 1 ] ], 
    instances: [ undefined, undefined ],
    invocationCallOrder: [ 1, 2 ],
    results: [
    	{ type: 'return', value: undefined },
        { type: 'return', value: 2 } // mock.mockReturnValue[Once](2); 처럼 값을 넣는다면..
    ],
    lastCall: [ 1 ]
}

test 시도 해본 것 수정 중...

  • 케이스와 expect() 을 최대한 적게 해야한다.
jest.mock('../app/controllers/likes.controller.js'); // 해당 대상을 Mocking 화
import likesController from '../app/controllers/likes.controller.js';

jest.mock('../app/service/likes.service.js');
import likesService from '../app/service/likes.service.js';

jest.mock('../app/database/connection.js');
import Connection from '../app/database/connection.js';

jest.mock('../app/database/query.js');
import Query from '../app/database/query.js';


//describe , test 할 것들을 묶는 큰 목록
describe('Mockiong 정리', () => {
    describe('likesController middleware Test', () => {
        const res = {
            send: jest.fn(),
            status: jest.fn(() => res),
            json: jest.fn(),
        };

        // it/test(테스트 이름: String, fn) 단위 테스트 케이스
        test('좋아요를 눌렀을 때 필드 값이 비어있는 경우', async() => {
            const req = {
                errors: [] // id, params 이 없어서 validation 단계에서 에러 났다고 가정
            };

            await likesController.createLikes(req, res);
            res.status.mockReturnValue(400);
            
            expect(res.status()).toBe(400);
        });

        test('좋아요를 눌렀을 때 정상적으로 추가 되었을 경우', async() => {
            const req = {
                id: 1,
                params: { id: 1 },
            };

            // 정상적인 케이스로 실행 시 return 될 값을 임의로 작성
            const result = {
                success: true,
                response: 'insert된 결과 값',
                // error는 null 이라 중요한 것이 아니라서 체크하지 않음.
            }

            res.json.mockReturnValue(result);

            await likesController.createLikes(req, res);
            
            expect(res.json().success).toBeTruthy(); // 값이 참인지 확인
            expect(res.json().response).toMatch(/insert된 결과 값/);
        });

        test('좋아요를 눌렀을 때 좋아요 실패 했을 경우', async() => {
            const req = {
                id: 1,
                params: { id: 1 },
            };

            // 정상적인 케이스로 실행 시 return 될 값을 임의로 작성
            const result = {
                success: false,
                // response는 null 이라 중요한 것이 아니라서 체크하지 않음.
                error: {
                    status: 500,
                    message: '[LikesService] Error create likes',
                },
            }

            res.status.mockReturnValue(500);
            res.json.mockReturnValue(result);
            
            await likesController.createLikes(req, res);
            
            expect(res.status()).toBe(500); // 주소 및 값이 같은 객체인지 확인.
            expect(res.json().success).toBeFalsy(); // Falsy -> false 인지 확인.
            expect(res.json().error.message).toMatch(/LikesService/); // toMatch(/Match 되야하는 문자열/)
        });
    });



    describe('likesService Func Test', () => {
        test('findLikes() 호출', async() => {
            const nftId = 1;
            const walletId = 1;
            const result = {
                nftId: nftId,
                walletId: walletId,
            };

            likesService.findLikes.mockReturnValue(result); // 원하는 반환 값을 설정.
            
            const findLikes =  await likesService.findLikes(nftId, walletId);  // 필요한 값을 넣었다고 가정.
            
            expect(findLikes).toHaveProperty('nftId', 1);
            expect(findLikes).toHaveProperty('walletId', 1);
        });

        // 보류
        test.skip('findLikes() 에러', async() => {
            const nftId = 1;
            const walletId = 1;

            likesService.findLikes.mockReturnValue(() => {throw new Error('[LikesService] Error finding likes')}); // 원하는 반환 값을 설정.
            
            console.log(likesService.findLikes(nftId, walletId));

            expect( () => { 
                    const result = likesService.findLikes(nftId, walletId);
                }).toThrowError(/[LikesService] Error finding likes/);
        });

        test('createLikes() 호출', async() => {
            const nftId = 566;
            const walletId = 215;
            const result = {
                nftId: nftId,
                walletId: walletId,
            };

            likesService.createLikes.mockReturnValue(result); // 원하는 반환 값을 설정.
            
            const findLikes =  await likesService.createLikes(nftId, walletId);  // 필요한 값을 넣었다고 가정.
            
            expect(findLikes).toHaveProperty('nftId', 566);
            expect(findLikes).toHaveProperty('walletId', 215);
        });
    });

    describe('likes Query Test', () => {
        let conn;

        beforeAll( async() => {
            conn = await Connection.getKnex();
        });

        afterAll( async()=> {
            await Connection.closeAllConnections();
        });

        test('selectLikes() 실행', async () => {
            const params = {
                nftId: 566,
                walletId: 215,
            }

            Query.selectOneLikes.mockReturnValue(params);
            const likeResult = await Query.selectOneLikes(conn, params);

            expect(likeResult).toHaveProperty('nftId', 566);
            expect(likeResult).toHaveProperty('walletId', 215);
        });

        test('insertLikes() 실행', async () => {
            const params = {
                nftId: 566,
                walletId: 215,
            }

            Query.insertLikes.mockReturnValue(1);
            const likeResult = await Query.insertLikes(conn, params);

            expect(likeResult).toBe(1);
        });
    });
});

참고

ESM, ECMAScript Modules

ECMAScript Modules - jest docs
jest.mock은 Babel #10025 없이 ES 모듈을 조롱하지 않습니다 .
How to use ESM tests with jest
Jest import (ESM)기능 활성화하기 (with 프로그래머스 과제관)

jest mock 사용하기

  • jest.config.js
export default async () => {
    return {
        transform: {},
    };
};
  • package.js
    • node --experimental-vm-modules node_modules/jest/bin/jest.js 추가
"scripts": {
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
},
  • Connection.test.js
import { jest, beforeAll, describe, test, expect } from '@jest/globals';

jest.mock('../utils/connection.js');

let Connection;

beforeAll( async () => {
    ({ default: Connection } = await import('../utils/connection.js') );
})

describe('test connection.js', () => {
    test('knex connect test', async () => {
        const mockGetKnex = jest.spyOn(Connection, 'getKnex');
        const expectResult = "Query execution";
        mockGetKnex.mockResolvedValue(expectResult);

        const conn = await Connection.getKnex();
        console.log('invocationCallOrder: ', mockGetKnex.mock.invocationCallOrder);
        expect(mockGetKnex).toHaveBeenCalled();
        expect(conn).toBe(expectResult);

        mockGetKnex.mockRestore();
    });
})

jest.spyOn(object, methodName) : 원래 함수를 덮어쓰고 함수를 감시하는 spy 함수를 만들어서 적용한다.

import {jest} from '@jest/globals'; 를 이용할 때 Mock Function 확인

에러

Cannot log after tests are done. Did you forget to wait for something async in your test?

-> async / await 로 감싸야한다.

test.only("should not pass", async (done) => {
    try {
      const a = await getAsync()
      expect(a).toEqual(2)
      done()
    } catch (e) {
      // have to manually handle the failed test with "done.fail"
      done.fail(e)
    }
  })

Returning a Promise from "describe" is not supported. Tests must be defined synchronously.

  • describe 에서는 Promise 지원하지 않는다 했음.
  • test() 에 함수를 넘겨 줄때 Promise 형식으로 넘겨 주면 되었음.

Jest did not exit one second after the test run has completed.

  • 해결 방법 : 비동기에서 사용 중인 자원(DB Connection) 이 존재했는데 사용한 뒤에 자원을 회수하면 되었습니다.
    • --detectOpenHandles 은 디버깅 시 사용하며 그 성능이 많이 저하된다고 하기 때문에 사용하지 않는다고 합니다.

Jest did not exit one second after the test run has completed.

This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue.
[error] Jest did not exit one second after the test run has completed. 해결

import walletService from '../app/service/wallet.service.js';
import 'regenerator-runtime/runtime';
import conn from '../app/database/connection.js';

describe('address 와 blockchainId 로 wallet 조회하기', () => {

    test('조회가 되는 경우', async () => {
        // given
        const address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A300b00';
        const blockchainId = 1;
    
        try {
            // when
            const wallet = await walletService.findByAddressAndBlockchainId(address, blockchainId);

            // then
            expect(wallet.address).toEqual('0xFDEa65C8e26263F6d9A1B5de9555D2931A300b00');
            expect(wallet.blockchainId).toEqual(1);
        } catch (err) {
            // then
            expect(String(err)).toMatch("Error: [WalletService] Error finding wallet");
        } finally {
            // walletService.findByAddressAndBlockchainId 사용시 만들어진 DB connectino 자원 회수
            conn.closeAllConnections();
        }
    })
})
  • 첫 번째

  • 두 번째

Matcher error: received value must be a function

  • expect(() => compileAndroidCode()).toThrow(에러 내용이 들어갑니다.) : toThrow() 을 사용할 때는 expect() 인자 값으로 function 이 들어가야 한다.

form file mock

form file mock

Jest Mock 정보 확인

Jest 강좌 #5 목 함수(Mock Functions) - 자바스크립트 테스트 프레임워크;

  • 테스트 예제 코드
const mockFn = jest.fn();

mockFn();
mockFn(1); // 호출될 때 인수 전달, 여러개 가능
mockFn.mockReturnValueOnce(1);

test("mockFn 구경", () => {
    console.log(mockFn);
    console.log(mockFn.mock);
    console.log(mockFn.mock.results);
});

test("함수는 2번 호출됩니다.", () => {
    expect(mockFn.mock.calls.length).toBe(2);
});

test("2번째로 호출된 함수에 전달된 첫 번째 인수는 1 입니다.", () => {
    expect(mockFn.mock.calls[1][0]).toBe(1);
});


/**
 * Mock Function 으로 기능 동작하는 코드인지 확인하기
 */
const mockFn1 = jest.fn();

function forEachAdd1(arr) {
    arr.forEach(num => {
        // fn(num+1)
        mockFn1(num + 1);
    })
}

forEachAdd1([10,20,30])

test("함수 호출은 3번 됩니다.", () => {
    expect(mockFn1.mock.calls.length).toBe(3);
})

test("전달된 값은 11, 21, 31 입니다.", () => {
    expect(mockFn1.mock.calls[0][0]).toBe(11);
    expect(mockFn1.mock.calls[1][0]).toBe(21);
    expect(mockFn1.mock.calls[2][0]).toBe(31);
})

// mock function
const mockFn2 = jest.fn(num => num + 1);

mockFn2(10)
mockFn2(20)
mockFn2(30)

describe("MockFn2", () => {
    test("함수 호출은 3번 됩니다.", () => {
        console.log(mockFn2.mock.calls);
        console.log(mockFn2.mock.results);
        expect(mockFn2.mock.calls.length).toBe(3);
    });

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

/**
 * Return 값 확인하기
 */
const mockFn3 = jest.fn();

mockFn3
.mockReturnValueOnce(10)
.mockReturnValueOnce(20)
.mockReturnValueOnce(30)
.mockReturnValue(40)

mockFn3();
mockFn3();
mockFn3();
mockFn3();

describe('MockFn3', () => {
    test('results 확인', () => {
        console.log(mockFn3.mock.results);
    });
})


/**
 * Mock 비동기 함수 흉내
 */
const mockFn4 = jest.fn();

mockFn4.mockResolvedValue({ name: "Mike" });

describe('MockFn4', () => {
    test("받아온 이름은 Mike", () => {
        mockFn4().then(res => {
            expect(res.name).toBe("Mike");
        });
    });
})


/**
 * 외부 모듈 사용하는 것 테스트 하기
 */

import fn from './fn.js';

jest.mock('./fn.js'); // mock 객체로 만들어준다.

// mocking 화를 시켜서 실제 코드를 실행시키지는 않는다.
fn.createUser.mockReturnValue({ name: "Mike" });

describe('외부 모듈 테스트', () => {
    test('유저를 만든다', () => {
        const user = fn.createUser("Mike");
        expect(user.name).toBe("Mike");
    });
});

/** 
 * 유용한 것
*/
const mockFn5 = jest.fn();

mockFn5(10, 20);
mockFn5();
mockFn5(30, 40);

describe('MockFn5', () => {
    test("한번 이상 호출?", () => { // 호출 여부
        expect(mockFn5).toBeCalled();
    });
    test("정확히 세번 호출?", () => { // 정확한 호출 횟수
        expect(mockFn5).toBeCalledTimes(3);
    });
    test("10이랑 20 전달받은 함수가 있는가?", () => { // 인수로 어떤걸 받았는지 체크
        expect(mockFn5).toBeCalledWith(10, 20);
        expect(mockFn5).toBeCalledWith(30, 40);
    });
    test("마지막 함수는 30이랑 40 받았음?", () => { // 마지막에 받은 인수 체크
        expect(mockFn5).lastCalledWith(30, 40);
        // expect(mockFn5).lastCalledWith(10, 20); // 마지막 이기 때문에 실패한다.
    });
});
  • test 로그 정보
console.log
    [Function: mockConstructor] {
      _isMockFunction: true,
      getMockImplementation: [Function (anonymous)],
      mock: [Getter/Setter],
      mockClear: [Function (anonymous)],
      mockReset: [Function (anonymous)],
      mockRestore: [Function (anonymous)],
      mockReturnValueOnce: [Function (anonymous)],
      mockResolvedValueOnce: [Function (anonymous)],
      mockRejectedValueOnce: [Function (anonymous)],
      mockReturnValue: [Function (anonymous)],
      mockResolvedValue: [Function (anonymous)],
      mockRejectedValue: [Function (anonymous)],
      mockImplementationOnce: [Function (anonymous)],
      mockImplementation: [Function (anonymous)],
      mockReturnThis: [Function (anonymous)],
      mockName: [Function (anonymous)],
      getMockName: [Function (anonymous)]
    }

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

  console.log
    {
      calls: [ [], [ 1 ] ],
      instances: [ undefined, undefined ],
      invocationCallOrder: [ 1, 2 ],
      results: [
        { type: 'return', value: undefined },
        { type: 'return', value: undefined }
      ],
      lastCall: [ 1 ]
    }

      at Object.<anonymous> (test/fn.test.js:9:13)

  console.log
    [
      { type: 'return', value: undefined },
      { type: 'return', value: undefined }
    ]

      at Object.<anonymous> (test/fn.test.js:10:13)

  console.log
    [ [ 10 ], [ 20 ], [ 30 ] ]

      at Object.<anonymous> (test/fn.test.js:55:17)

  console.log
    [
      { type: 'return', value: 11 },
      { type: 'return', value: 21 },
      { type: 'return', value: 31 }
    ]

      at Object.<anonymous> (test/fn.test.js:56:17)

  console.log
    [
      { type: 'return', value: 10 },
      { type: 'return', value: 20 },
      { type: 'return', value: 30 },
      { type: 'return', value: 40 }
    ]

      at Object.<anonymous> (test/fn.test.js:89:17)

 PASS  test/fn.test.js
  ✓ mockFn 구경 (14 ms)
  ✓ 함수는 2번 호출됩니다. (1 ms)2번째로 호출된 함수에 전달된 첫 번째 인수는 1 입니다.
  ✓ 함수 호출은 3번 됩니다. (2 ms)
  ✓ 전달된 값은 11, 21, 31 입니다.
  MockFn2
    ✓ 함수 호출은 3번 됩니다. (1 ms)10에서 1 증가한 값이 반환된다
    ✓ 20에서 1 증가한 값이 반환된다 (1 ms)20에서 1 증가한 값이 반환된다
  MockFn3
    ✓ results 확인
  MockFn4
    ✓ 받아온 이름은 Mike
  외부 모듈 테스트
    ✓ 유저를 만든다
  MockFn5
    ✓ 한번 이상 호출?
    ✓ 정확히 세번 호출? (1 ms)10이랑 20 전달받은 함수가 있는가?
    ✓ 마지막 함수는 30이랑 40 받았음?

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |    7.69 |      100 |       0 |    7.69 |                   
 fn.js    |    7.69 |      100 |       0 |    7.69 | 2-30              
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       16 passed, 16 total
Snapshots:   0 total
Time:        0.288 s, estimated 1 s

통합 테스트 supertest

[JEST] 📚 supertest (api 요청테스트)

profile
끄적끄적 쓰는곳

0개의 댓글