@dannysir/js-te
v0.3.1
Published
JavaScript test library
Maintainers
Readme
js-te
Jest에서 영감을 받아 만든 가벼운 JavaScript 테스트 프레임워크입니다.
📎 최근 업데이트 0.3.0v
mock 이후 import를 해야하는 문제 해결
- 문제 : 기존의 경우 모킹 기능 이용시 반드시 동적 import문을 mock 다음에 작성해야 했음
- 해결
- 기존
mockStore를 직접 비교하여 import하는 방식에서 wrapper 패턴을 이용하도록 적용 - 모듈을 새로운 함수로 만들어 함수를 실행할 때마다
mockStore와 비교하여 값을 리턴하도록 수정
- 기존
모듈 변환 최적화
- 문제 : 앞선 변경으로 인해 모든 파일의 모듈들이 사용될 때마다
mockStore와 비교하는 로직이 실행됨 - 해결
cli로직에 mock을 미리 검사하여 mock 경로를 미리 저장하는 로직을 추가- 미리 확인한 mock 경로를 이용해 import문이 만약 저장된 경로일 때만 babel 변환
설치
npm install --save-dev @dannysir/js-te빠른 시작
1. 테스트 파일 만들기
*.test.js 파일을 만들면 자동으로 찾아서 실행합니다.
별도의 import문 없이 describe와 test, expect 로직이 사용 가능합니다.
// math.test.js
describe('[단순 연산 테스트]', () => {
test('더하기 테스트', () => {
expect(1 + 2).toBe(3);
});
});2. 테스트 실행
package.json에 추가.
- ~~type을 module로 설정해주세요.~~
✅ 0.2.1 버전부터 common js 방식을 지원합니다.
{
"type": "module", // 0.2.1 버전부터 common js도 사용 가능
"scripts": {
"test": "js-te"
}
}실행:
npm test예시 출력 화면
API
테스트 작성
test(설명, 함수)
테스트 하나를 정의합니다.
test('배열 길이 확인', () => {
expect([1, 2, 3].length).toBe(3);
});describe(이름, 함수)
테스트를 그룹으로 묶습니다. 중첩도 됩니다.
describe('계산기', () => {
describe('더하기', () => {
test('양수 더하기', () => {
expect(2 + 3).toBe(5);
});
});
describe('빼기', () => {
test('양수 빼기', () => {
expect(5 - 3).toBe(2);
});
});
});Matcher
expect(값).toBe(기댓값)
===로 비교합니다. 숫자, 문자열 같은 원시값 비교할 때 사용.
expect(5).toBe(5);
expect('안녕').toBe('안녕');expect(값).toEqual(기댓값)
객체나 배열의 내용을 비교합니다.
expect({ name: '철수' }).toEqual({ name: '철수' });
expect([1, 2, 3]).toEqual([1, 2, 3]);expect(함수).toThrow(에러메시지)
함수가 에러를 던지는지 확인합니다.
expect(() => {
throw new Error('에러 발생');
}).toThrow('에러');expect(값).toBeTruthy() / expect(값).toBeFalsy()
참/거짓 여부를 확인합니다.
expect(true).toBeTruthy();
expect(0).toBeFalsy();Mocking
동작 원리
Babel을 사용해서 import/require 구문을 변환하여 mock 함수를 가져오도록 했습니다.
- 테스트 파일 찾기
mock(path, mockObj)선언 확인path를 key로 이용해 Map에 저장
- Babel로 코드 변환
// 0.3.0 버전 이후
const _original = await import('./random.js');
const random = (...args) => {
const module = mockStore.has('/path/to/random.js')
? { ..._original, ...mockStore.get('/path/to/random.js') }
: _original;
return module.random(...args);
};
// 0.3.0 버전 이전
const _original = await import('./random.js');
const _module = mockStore.has('/path/to/random.js')
? { ..._original, ...mockStore.get('/path/to/random.js') }
: _original;
const {random1, random2} = _module;- 테스트 실행
- 원본 파일 복구
mock(모듈 절대 경로), mock객체)
모듈을 모킹합니다. import 하기 전에 호출해야 합니다.
🚨 주의사항
- 반드시 경로는 절대 경로로 입력해주세요.
- babel이 import문에서 절대 경로로 변환하여 확인을 하기 때문에 반드시 절대 경로로 등록해주세요.
- ~~import문을 반드시 mocking 이후에 선언해주세요.~~
- ~~mocking 전에 import를 하게 되면 mocking되기 전의 모듈을 가져오게 됩니다.~~
0.3.0 버전부터 import문을 mock선언 이후에 하지 않아도 됩니다.
💡 부분 모킹(Partial Mocking)
0.2.1 버전부터 모듈의 일부 함수만 모킹하고 나머지는 원본을 사용할 수 있습니다.
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
// math.test.js
const { add, multiply } = import('./math.js'); // 0.3.0 버전부터는 최상단에 선언 가능
test('부분 모킹 예제', async () => {
mock('/Users/san/untitled/index.js', {
multiply: () => 100
});
expect(add(2, 3)).toBe(5); // 원본 함수 사용
expect(subtract(5, 3)).toBe(2); // 원본 함수 사용
expect(multiply(2, 3)).toBe(100); // 모킹된 함수 사용
});📦 모듈 시스템 지원
ESM(import)과 CommonJS(require) 모두 지원합니다.
// ESM 방식
import { random } from './random.js';
// CommonJS 방식
const { random } = require('./random.js');// random.js
export const random = () => Math.random();
// game.js
import { random } from './random.js'; // 자유롭게 import하면 babel에서 절대 경로로 변환하여 판단합니다.
// 또는 CommonJS 방식도 지원
// const { random } = require('./random.js');
export const play = () => random() * 10;
// game.test.js
import {play} from './game.js';
test('랜덤 함수 모킹', async () => {
// 1. 먼저 모킹
mock('/Users/san/Js-Te/test-helper/random.js', { // 반드시 절대 경로로 등록
random: () => 0.5
});
// 2. 모킹된 값 사용
expect(play()).toBe(5);
});clearAllMocks()
등록된 모든 mock을 제거합니다.
unmock(모듈경로)
특정 mock만 제거합니다.
isMocked(모듈경로)
mock이 등록되어 있는지 확인합니다.
each(cases)
cases를 배열로 받아 순차적으로 테스트 진행
🚨 주의 사항
cases는 반드시 Array 타입으로 받아야 합니다.
플레이스 홀더
- %s - 문자열/숫자
- %o - 객체 (JSON.stringify)
test.each([
[1, 2, 3, 6],
[3, 4, 5, 12],
[10, 20, 13, 43],
[10, 12, 13, 35],
])('[each test] - input : %s, %s, %s, %s', (a, b, c, result) => {
expect(a + b + c).toBe(result);
});
/* 출력 결과
✓ [each test] - input : 1, 2, 3, 6
✓ [each test] - input : 3, 4, 5, 12
✓ [each test] - input : 10, 20, 13, 43
✓ [each test] - input : 10, 12, 13, 35
*/
test.each([
[{ name : 'dannysir', age : null}],
])('[each test placeholder] - input : %o', (arg) => {
expect(arg.name).toBe('dannysir');
});
/* 출력 결과
✓ [each test placeholder] - input : {"name":"dannysir","age":null}
*/beforeEach(함수)
각 테스트가 진행되기 전에 실행할 함수를 선언합니다.
중첩된 describe에서의 beforeEach는 상위 describe의 beforeEach를 모두 실행한 후, 자신의 beforeEach를 실행합니다.
describe('카운터 테스트', () => {
let counter;
beforeEach(() => {
counter = 0;
});
test('카운터 증가', () => {
counter++;
expect(counter).toBe(1);
});
test('카운터는 0부터 시작', () => {
expect(counter).toBe(0);
});
describe('중첩된 describe', () => {
beforeEach(() => {
counter = 10;
});
test('카운터는 10', () => {
expect(counter).toBe(10);
});
});
});테스트 파일 찾기 규칙
자동으로 다음 파일들을 찾아서 실행합니다:
*.test.js파일test/폴더 안의 모든.js파일
프로젝트/
├── src/
│ ├── utils.js
│ └── utils.test.js ✅
├── test/
│ ├── integration.js ✅
│ └── e2e.js ✅
└── calculator.test.js ✅예제
기본 테스트
describe('문자열 테스트', () => {
test('문자열 합치기', () => {
const result = 'hello' + ' ' + 'world';
expect(result).toBe('hello world');
});
test('대문자 변환', () => {
expect('hello'.toUpperCase()).toBe('HELLO');
});
});모킹 예제
전체 모킹
// mocking.test.js
import {random} from '../src/test-helper/game.js'; // 0.2.4 버전부터 import문 상단 배치 가능
test('[mocking] - mocking random function', async () => {
mock('/Users/san/Js-Te/test-helper/random.js', {
random: () => 3,
});
// 0.3.0 버전 이전까지는 반드시 mock 이후 동적 import문 작성
// const {play} = await import('../src/test-helper/game.js');
expect(play()).toBe(30);
});
// game.js
import {random} from '/test-helper/random.js'
export const play = () => {
return random() * 10;
};
// random.js
export const random = () => Math.random();부분 모킹
// calculator.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
// calculator.test.js
test('[partial mocking] - mock only multiply', async () => {
// multiply만 모킹, add와 subtract는 원본 사용
mock('/Users/san/Js-Te/calculator.js', {
multiply: (a, b) => 999
});
const { add, subtract, multiply } = await import('./calculator.js');
expect(add(2, 3)).toBe(5);
expect(subtract(5, 2)).toBe(3);
expect(multiply(2, 3)).toBe(999);
});링크
만든 이유
Jest를 사용하며 JavaScript 테스트 라이브러리의 구조가 궁금하여 만들게 되었습니다.
라이선스
ISC
