작성한 코드가 원하는 동작을 하는지 확인하는 검증 과정을 말한다.
Test Runners
Testing Frameworks
Assertion Libraries
Testing Plugins
Test Runners
,Testing Frameworks
,Assertion Libraries
,Testing Plugins
가 있다.자바스크립트 테스트는 브라우저 환경
과 Node.js 환경
에서 실행할 수 있다.
브라우저 환경과는 달리, Node.js 환경에서 돌아가는 테스트 러너들은 테스트 러너의 실행 환경과 테스트 코드의 실행 환경을 구분할 필요가 없기 때문에 (둘다 Node.js) 대부분 테스트 프레임워크로써 통합된 형태로 제공된다.
즉, Mocha
, Jest
, Jasmine
은 Node.js 환경에서 돌아가는 테스트 프레임워크이다.
npm trends를 보면 Jest가 Test Runner
와 Test Mathcher
, Test Mock
프레임워크까지 제공해주기 때문에 많은 개발자가 사용하는 것을 알 수 있다.
타입스크립트, React, 바벨 사용 등의 추가 종속성이 필요하다면 아래의 Jest 공식문서를 보면 설치할 수 있다.
https://jestjs.io/docs/getting-started
기본적인 jest 설치는 아래와 같다.
npm install --save-dev jest
설치 후 package.json 파일을 열어 test 스크립트를 jest로 추가해준다.
"test" :"jest"
npm test
명령어 입력 시 테스트 스크립트가 실행된다.
Jest는 기본적으로 test.js
로 끝나거나(ex> component.test.js) __**test__**
디렉터리 안에 있는 파일들을 모두 테스트 파일로 인식한다.
만약 특정 테스트 파일만 실행하고 싶은 경우에는 npm test <파일명 이나 경로>
를 입력하여 실행할 수 있다.
test("테스트 설명", () => {
expect(실행할 함수 또는 값).(method);
});
describe("특정 범위 관련 작업", () => {
test("테스트 설명", () => {
expect(실행할 함수 또는 값).(method);
});
});
테스트를 실행하면 터미널에 위 사진처럼 실패한 테스트, 성공한 테스트가 출력된다.
test("2 더하기 3은 5", () => {
expect(2 + 3).toBe(5);
});
const user = {
name: 'dobby',
age: 25,
}
test("user 객체 비교", () => {
expect(user).toEqual({
name: 'dobby',
age: 25,
})
});
toEqual보다는 깊은 비교를 위해 toStrictEqual을 사용하는 것이 좋다.const user = {
name: 'dobby',
age: 25,
gender: undefined
}
test("user 객체 비교", () => {
expect(user).toEqual({
name: 'dobby',
age: 25,
})
}); // 성공
test("user 객체 비교", () => {
expect(user).toStrictEqual({
name: 'dobby',
age: 25,
})
}); // 실패
test("0.1 + 0.2 = 0.3", () => {
expect(0.1 + 0.2).toBeCloseTo(0.3);
});
test("Hello world에 a라는 글자가 있나?", () => {
expect("Hello world").toMatch(/a/);
}); // 실패
test("Hello world에 h라는 글자가 있나?", () => {
expect("Hello world").toMatch(/h/i);
}); // 성공
test("Hello world에 h라는 글자가 있나?", () => {
const user = "mike";
const userList = ["tom", "jane", "kai"];
expect(userList).toContain(user);
}); // 실패
const makeError = () => {
throw new Error("[Error] 에러 발생!");
}
test("에러 발생", () => {
expect(() => makeError()).toThrow();
}); // 성공
test("에러 발생", () => {
expect(() => makeError()).toThrow("[Error]");
}); // 성공, 특정 에러인지 확인
// 비동기 코드 실행 후 에러 발생 확인
test("에러 발생", async () => {
const app = new App();
await expect(app.run()).rejects.toThrow("[Error]");
}); // 성공, 특정 에러인지 확인
const fn = require("./fn");
test("3초 후에 받아온 이름은 mike", (done) => {
function callback(name) {
expect(name).toBe("Tom");
done(); // done이 호출되기 전까지 Jest는 실행을 끝내지 않는다.
}
fn.getName(callback);
});
만약, done을 전달은 받았는데, 호출하지 않으면 테스트가 실패하게 된다.
promise를 넘겨주면 jest는 실행이 끝날 때까지 기다려준다.
const getAge() => {
const age = 30;
return new Promise((res, rej) => {
setTimeout(() => {
res(age);
// throw new Error("[ERROR]");
}, 3000);
})
}
// promise를 사용할 떄는 return을 사용해줘야 한다.
test("3초 후 나이 30", () => {
return getAge().then(age => {
expect(age).toBe(30);
});
});
// matcher를 사용했을 때
test("3초 후 나이 30", () => {
expect(age).resolves.toBe(30);
// expect(age).rejects.toThrow("[ERROR]");
});
test("3초 후 나이 30", async () => {
const age = await getAge();
expect(age).toBe(30);
// await expect(age).resolves.toBe(30);
});
beforeEach(() => {
// 코드 작성
});
afterEach(() => {
// 코드 작성
});
beforeAll(() => {
// 실행
});
afterAll(() => {
// 실행
});
test("3초 후 나이 30", async () => {
const age = await getAge();
expect(age).toBe(30);
// await expect(age).resolves.toBe(30);
});
test("3초 후 나이 30", async () => {
const age = await getAge();
expect(age).toBe(30);
// await expect(age).resolves.toBe(30);
});
test.only("3초 후 나이 30", async () => {
const age = await getAge();
expect(age).toBe(30);
// await expect(age).resolves.toBe(30);
});
test.skip("3초 후 나이 30", async () => {
const age = await getAge();
expect(age).toBe(30);
// await expect(age).resolves.toBe(30);
});
test("3초 후 나이 30", async () => {
const age = await getAge();
expect(age).toBe(30);
// await expect(age).resolves.toBe(30);
});
테스트 하기 위해 흉내만 내는 함수
mocking이란 단위 테스트를 작성할 때, 해당 코드가 의존하는 부분을 가짜(mock)로 대체하는 기법을 말한다.
일반적으로 테스트하려는 코드가 의존하는 부분을 직접 생성하기가 너무 부담스러운 경우 mocking을 많이 사용한다.
예를 들어, 데이터베이스에서 데이터를 삭제하는 코드에 대한 단위 테스트를 작성할 때, 실제 데이터베이스를 사용한다면 여러 문제점이 발생할 수 있다.
jest.fn()
으로 mock 함수를 만들 수 있다.
const mockFn = jest.fn();
mockFn();
mockFn(1);
mockFn(11);
// mockFn.mock 에는 호출되었던 값들이 저장된다.
// [[], [1], [11]]
// mockFn.mock.calls
// 모든 호출의 호출 인수를 포함하는 배열
// 이 메소드로 알 수 있는 것은 두 가지이다.
// 1. 함수가 총 몇 번 호출되었는가
// 2. 호출될 때 전달된 인수는 무엇인가
test("call 길이", () => {
console.log(mockFn.mock.results); // type과 value가 들어있는 배열
expect(mockFn.mock.calls.length).toBe(3);
expect(mockFn.mock.calls[1][0].toBe(1);
expect(mockFn.mock.results[2].value.toBe(11);
}); // 성공
// mockFn.mock.results 결과값
/*
[
{
type: 'return',
value: ,
},
{
type: 'return',
value: 1
},
{
type: 'return',
value: 11,
},
];
*/
실행할 때마다 각각 다른 값을 리턴해줄 수도 있다.
const mockFn = jest.fn();
mockFn
.mockReturnValueOnce(10)
.mockReturnValueOnce(20);
.mockReturnValue(30);
mockFn();
mockFn();
mockFn();
test("call 길이", () => {
console.log(mockFn.mock.results);
expect(mockFn.mock.calls.length).toBe(3);
}); // 성공
const mockFn = jest.fn();
mockFn
.mockReturnValueOnce(true);
.mockReturnValueOnce(false);
.mockReturnValueOnce(true);
.mockReturnValueOnce(false);
.mockReturnValueOnce(true);
const result = [1, 2, 3, 4, 5].filter(num => mockFn(num));
test("홀수는 1,3,5", () => {
expect(result).toStrictEqual([1,3,5]);
}); // 성공
비동기 함수를 흉내낼 수도 있다.
const mockFn = jest.fn();
mockFn.mockResolvedValue({name: 'mike'});
test("받아온 이름은 Mike", () => {
mockFn().then(res => {
expect(res.name).toBe("mike");
});
}); // 성공
test('async test', async () => {
const asyncMock = jest
.fn()
.mockResolvedValue('default')
.mockResolvedValueOnce('first call')
.mockResolvedValueOnce('second call');
await asyncMock(); // 'first call'
await asyncMock(); // 'second call'
await asyncMock(); // 'default'
await asyncMock(); // 'default'
});
// 항상 거부하는 비동기 mock 함수
test('async test', async () => {
const asyncMock = jest
.fn()
.mockRejectedValue(new Error('Async error message'));
await asyncMock(); // throws 'Async error message'
});
test('async test', async () => {
const asyncMock = jest
.fn()
.mockResolvedValueOnce('first call')
.mockRejectedValueOnce(new Error('Async error message'));
await asyncMock(); // 'first call'
await asyncMock(); // throws 'Async error message'
});
const mockFn = jest.fn();
mockFn(10, 20);
mockFn();
mockFn(30, 40);
test("한 번이라도 호출됐으면 통과", () => {
expect(mockFn).toBeCalled();
}); // 성공
test("3번 호출됐으면 통과", () => {
expect(mockFn).toBeCalledTimes(3);
}); // 성공
test("호출한 값이 포함하면 통과", () => {
expect(mockFn).toBeCalledWith(10, 20);
}); // 성공
test("마지막으로 호출된 함수의 인수가 같으면 통과", () => {
expect(mockFn).lastCalledWith(30, 40);
}); // 성공
mockFn.mockImplementation(fn)
jest.fn(implementation)은 jest.fn().mockImplementation(implementation)과 같다.
const mockFn = jest.fn(scalar => 42 + scalar);
mockFn(0); // 42
mockFn(1); // 43
mockFn.mockImplementation(scalar => 36 + scalar);
mockFn(2); // 38
mockFn(3); // 39
---------------------------------------------------
const mockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');
mockFn(); // 'first call'
mockFn(); // 'second call'
mockFn(); // 'default'
mockFn(); // 'default'
const mockFn = jest.fn().mockName('mockedFunction');
// mockFn();
expect(mockFn).toHaveBeenCalled();
expect(mockedFunction).toHaveBeenCalled()
/*
Expected number of calls: >= 1
Received number of calls: 0
*/
mock 함수가 호출될 때마다 반환되는 값을 받는다.
// jest.fn().mockImplementation(() => value);
const mock = jest.fn();
mock.mockReturnValue(42);
mock(); // 42
mock.mockReturnValue(43);
mock(); // 43
// mockReturnValueOnce를 먼저 출력하고, 더이상 출력할 Once가 없으면
// mockReturnValue를 출력한다.
const mockFn = jest
.fn()
.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call');
mockFn(); // 'first call'
mockFn(); // 'second call'
mockFn(); // 'default'
mockFn(); // 'default'
어떤 객체에 속한 함수의 구현을 가짜로 대체하지 않고, 해당 함수의 호출 여부와 어떻게 호출되었는지만을 알아내야 할 때 사용한다.
jest.spyOn(object, methodName)
const calculator = {
add: (a, b) => a + b,
};
const spyFn = jest.spyOn(calculator, "add");
const result = calculator.add(2, 3);
expect(spyFn).toHaveBeenCalledTimes(1);
expect(spyFn).toHaveBeenCalledWith(2, 3);
expect(result).toBe(5);
// 모두 통과
jest.spyOn()
함수를 이용해서 calculator
객체의 add
라는 함수에 스파이를 붙였다.
따라서 add
함수 호출 후에 호출 횟수와 어떤 인자가 넘어갔는지 검증할 수 있다.
하지만 가짜 함수로 대체한 것은 아니기 때문에 여전히 결과 값은 원래 구현대로 2와 3의 합인 5가 된다.
테스트 대역은 구현 코드를 테스트하는데 필요한 것(객체, 함수, 데이터 등)들을 테스트를 실행하는 동안 대신하는 요소들을 말한다. 이는 mock을 의미한다.
다음 테스트 케이스를 실행하기 전에는 현재 테스트 케이스에서 사용했던 Mock을 정리해주는게 좋다.
다음 테스트 케이스에 영향을 줄 수도 있기 때문이다.
수동으로 mock을 정리하는 방법
jest
로 mock 정리하기
mockFn.clearAllMocks
호출)mocks.calls
와 mock.instances
를 초기화한다.mockFn.resetAllMocks
호출)mockClear (clearMocks)
함수가 하는 일을 모두 할 수 있다.mockFn.restoreAllMocks
호출)mockReset (resetMocks)
함수가 하는 일을 모두 할 수 있다.mockRestore(restoreMocks)
를 호출하여 원래대로 되돌려줘야 한다.beforeEach
, afterEach
와 같은 함수로 테스트 케이스가 실행되기 전이나 후에 정리해주는 방법도 있다.
beforeEach(() => {
jest.restoreAllMocks();
});
프리코스 1-4주차 미션에서 제공된 테스트 코드를 분석해보자.
import { MissionUtils } from '@woowacourse/mission-utils';
import App from '../src/App.js';
const mockQuestions = (inputs) => {
// MissionUtils.Console.readLineAsync: 사용자로부터 데이터를 입력받는 함수
MissionUtils.Console.readLineAsync = jest.fn(); // mockFn 할당
// mock 함수로 할당한 MissionUtils.Console.readLineAsync이 호출될 때마다 실행되도록 한다.
MissionUtils.Console.readLineAsync.mockImplementation(() => {
const input = inputs.shift(); // 배열로 전달받은 데이터 추출
return Promise.resolve(input); // Promise는 return 해줘야 정상작동 한다.
});
};
const getLogSpy = () => {
// spyOn(객체, 메소드 이름)
// MissionUtils.Console의 print 메서드를 지켜본다.
const logSpy = jest.spyOn(MissionUtils.Console, 'print');
logSpy.mockClear(); // mock 데이터를 지운다.
return logSpy;
};
describe('문자열 계산기', () => {
test('커스텀 구분자 사용', async () => { // async/await 사용
const inputs = ['//;\\n1'];
mockQuestions(inputs);
const logSpy = getLogSpy(); // MissionUtils.Console spy 리턴
const outputs = ['결과 : 1'];
const app = new App();
await app.run(); // App 클래스 안에서 readLineAsync, Console.print 사용
// readLineAsync 호출로 인한 입력값은 inputs로 전달받은 데이터로 대체
outputs.forEach((output) => {
// MissionUtils.Console.print가 output을 인자로 호출되었는지 확인
// expect.stringContaining: 전달받은 값이 인자로 받은 string과 일치하는지 여부를 확인
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output));
});
});
test('예외 테스트', async () => {
const inputs = ['-1,2,3'];
mockQuestions(inputs);
const app = new App();
// App을 실행하면서 비동기 rejects 처리되고 [ERROR] message를 가진 에러를 반환하는지 확인
await expect(app.run()).rejects.toThrow('[ERROR]');
});
});
// 입력을 받는 함수
const mockQuestions = (inputs) => {
MissionUtils.Console.readLineAsync = jest.fn(); // mockFn 할당
// mock 함수가 호출될 때마다 실행되도록 한다.
MissionUtils.Console.readLineAsync.mockImplementation(() => {
const input = inputs.shift(); // 받아온 입력값
return Promise.resolve(input); // 비동기로 전달
});
};
// 랜덤한 값을 만드는 함수
const mockRandoms = (numbers) => {
MissionUtils.Random.pickNumberInRange = jest.fn(); // mockFn 할당
// 초기값 할당 및 item에 대해 모두 적용하기 위해 reduce 사용
numbers.reduce((acc, number) => {
// mock 함수 호출 시 number 값을 차례로 반환한다.
// 초기값에 의해 아래와 같은 뜻이다.
// MissionUtils.Random.pickNumberInRange.mockReturnValueOnce(number);
return acc.mockReturnValueOnce(number);
}, MissionUtils.Random.pickNumberInRange);
};
const getLogSpy = () => {
// app에서 실행되는 MissionUtils.Console.print를 감시하여 호출 여부 판단 및 전달받은 인자 확인
// MissionUtils.Console의 가짜 함수
const logSpy = jest.spyOn(MissionUtils.Console, "print");
// mock에 대한 정보를 지운다.
logSpy.mockClear();
return logSpy;
};
test("기능 테스트", async () => {
const MOVING_FORWARD = 4; // 4이상이면 전진
const STOP = 3; // 3이하는 가만히
const inputs = ["pobi,woni", "1"]; // 사용자 입력
const logs = ["pobi : -", "woni : ", "최종 우승자 : pobi"]; // 출력값
const logSpy = getLogSpy(); // Console의 스파이 함수 리턴
mockQuestions(inputs); // 지정한 입력값을 전달
// inputs 배열의 아이템 값들을 mock 함수 실행시 차례로 반환
// 각 자동차가 전진할 것인지, 멈출 것인지를 지정
// MissionUtils.Random.pickNumberInRange가 호출되면 배열의 값들을 차례로 반환
mockRandoms([MOVING_FORWARD, STOP]);
// app 실행
const app = new App();
await app.run();
// app.js 파일에서 실행되는 Console을 감시
logs.forEach((log) => {
// app.js 파일을 실행한 결과의 출력에 log와 일치하는 것이 있는지 확인
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log));
});
});
test("예외 테스트", async () => {
const inputs = ["pobi,javaji"];
// 지정한 입력값 전달
mockQuestions(inputs);
const app = new App();
// app을 실행한 결과 [ERROR] 메시지를 가진 에러를 반환하는지 확인
await expect(app.run()).rejects.toThrow("[ERROR]");
});
테스트 돌린 결과
import { MissionUtils } from '@woowacourse/mission-utils';
import App from '../src/App.js';
const mockQuestions = (inputs) => {
MissionUtils.Console.readLineAsync = jest.fn();
MissionUtils.Console.readLineAsync.mockImplementation(() => {
const input = inputs.shift();
return Promise.resolve(input);
});
};
// 랜덤값 생성
const mockRandoms = (numbers) => {
// MissionUtils.Random.pickUniqueNumbersInRange: 1-45 사이 유니크한 랜덤값 생성
MissionUtils.Random.pickUniqueNumbersInRange = jest.fn(); // mockFn 할당
numbers.reduce(
// 초기값에 의해 MissionUtils.Random.pickUniqueNumbersInRange.mockReturnValueOnce(number)와 동일
(acc, number) => acc.mockReturnValueOnce(number),
MissionUtils.Random.pickUniqueNumbersInRange,
);
};
// app에서 실행되는 MissionUtils.Console.print를 감시하여 호출 여부 판단 및 전달받은 인자 확인
const getLogSpy = () => {
const logSpy = jest.spyOn(MissionUtils.Console, 'print');
logSpy.mockClear();
return logSpy;
};
// 예외 테스트
const runException = async (input) => {
const logSpy = getLogSpy();
const RANDOM_NUMBERS_TO_END = [1, 2, 3, 4, 5, 6];
const INPUT_NUMBERS_TO_END = ['1000', '1,2,3,4,5,6', '7'];
mockRandoms([RANDOM_NUMBERS_TO_END]); // [1, 2, 3, 4, 5, 6]을 랜덤한 값으로 지정
mockQuestions([input, ...INPUT_NUMBERS_TO_END]); // 전달받은 입력값을 먼저 리턴하도록 지정
const app = new App();
await app.run();
// [ERROR] message를 포함하는 에러를 반환하는지 확인
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('[ERROR]'));
};
describe('로또 테스트', () => {
// 각 테스트 전에
beforeEach(() => {
jest.restoreAllMocks(); // 각 테스트에 영향을 주지 않기 위해 mock 정리
});
test('기능 테스트', async () => {
const logSpy = getLogSpy();
// MissionUtils.Random.pickUniqueNumbersInRange이 호출될 때마다 각 배열을 리턴
mockRandoms([
[8, 21, 23, 41, 42, 43],
[3, 5, 11, 16, 32, 38],
[7, 11, 16, 35, 36, 44],
[1, 8, 11, 31, 41, 42],
[13, 14, 16, 38, 42, 45],
[7, 11, 30, 40, 42, 43],
[2, 13, 22, 32, 38, 45],
[1, 3, 5, 14, 22, 45],
]);
// 사용자가 입력할 값 설정
mockQuestions(['8000', '1,2,3,4,5,6', '7']);
const app = new App();
await app.run();
// 콘솔에 출력될 log
const logs = [
'8개를 구매했습니다.',
'[8, 21, 23, 41, 42, 43]',
'[3, 5, 11, 16, 32, 38]',
'[7, 11, 16, 35, 36, 44]',
'[1, 8, 11, 31, 41, 42]',
'[13, 14, 16, 38, 42, 45]',
'[7, 11, 30, 40, 42, 43]',
'[2, 13, 22, 32, 38, 45]',
'[1, 3, 5, 14, 22, 45]',
'3개 일치 (5,000원) - 1개',
'4개 일치 (50,000원) - 0개',
'5개 일치 (1,500,000원) - 0개',
'5개 일치, 보너스 볼 일치 (30,000,000원) - 0개',
'6개 일치 (2,000,000,000원) - 0개',
'총 수익률은 62.5%입니다.',
];
logs.forEach((log) => {
// 콘솔에 출력된 문자열이 logs의 각 아이템과 같은지 확인
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log));
});
});
test('예외 테스트', async () => {
await runException('1000j');
});
});
import { MissionUtils } from '@woowacourse/mission-utils';
import { EOL as LINE_SEPARATOR } from 'os';
import App from '../src/App.js';
const mockQuestions = (inputs) => {
const messages = [];
MissionUtils.Console.readLineAsync = jest.fn((prompt) => {
// 출력 메시지를 배열에 저장한다.
messages.push(prompt);
const input = inputs.shift();
if (input === undefined) {
throw new Error('NO INPUT');
}
return Promise.resolve(input);
});
MissionUtils.Console.readLineAsync.messages = messages;
};
// 전달받은 날짜를 MissionUtils.DateTimes.now의 반환값으로 설정한다.
const mockNowDate = (date = null) => {
// MissionUtils.DateTimes.now을 감시하여 호출 여부 판단 및 전달받은 인자 확인
const mockDateTimes = jest.spyOn(MissionUtils.DateTimes, 'now');
mockDateTimes.mockReturnValue(new Date(date));
return mockDateTimes;
};
// MissionUtils.Console.print를 감시하여 호출 여부 판단 및 전달받은 인자 확인
const getLogSpy = () => {
const logSpy = jest.spyOn(MissionUtils.Console, 'print');
logSpy.mockClear();
return logSpy;
};
// 각 배열의 인자값을 개행문자를 기준으로 join
const getOutput = (logSpy) => [...logSpy.mock.calls].join(LINE_SEPARATOR);
// 출력 문자열이 기대하는 문자열과 일치하는지 테스트
const expectLogContains = (received, expects) => {
expects.forEach((exp) => {
expect(received).toContain(exp);
});
};
const expectLogContainsWithoutSpacesAndEquals = (received, expects) => {
// 공백과 '=' 문자를 제외한 문자열이 기대하는 문자열과 일치하는지 테스트
const processedReceived = received.replace(/[\s=]/g, '');
expects.forEach((exp) => {
expect(processedReceived).toContain(exp);
});
};
const runExceptions = async ({
inputs = [],
inputsToTerminate = [],
expectedErrorMessage = '',
}) => {
const logSpy = getLogSpy();
mockQuestions([...inputs, ...inputsToTerminate]);
const app = new App();
await app.run();
// 전달받은 에러 메시지를 포함하여 출력하는지 테스트
expect(logSpy).toHaveBeenCalledWith(
expect.stringContaining(expectedErrorMessage),
);
};
const run = async ({
inputs = [],
inputsToTerminate = [],
expected = [],
expectedIgnoringWhiteSpaces = [],
}) => {
const logSpy = getLogSpy();
mockQuestions([...inputs, ...inputsToTerminate]);
const app = new App();
await app.run();
// 각 출력값을 개행문자를 기준으로 join
const output = getOutput(logSpy);
// expectedIgnoringWhiteSpaces 값이 있다면, 기대 문자열과 출력 문자열이 일치하는지 테스트
if (expectedIgnoringWhiteSpaces.length > 0) {
expectLogContainsWithoutSpacesAndEquals(
output,
expectedIgnoringWhiteSpaces,
);
}
// expected 값이 있다면, 기대 문자열과 출력 문자열이 일치하는지 테스트
if (expected.length > 0) {
expectLogContains(output, expected);
}
};
const INPUTS_TO_TERMINATE = ['[비타민워터-1]', 'N', 'N'];
describe('편의점', () => {
afterEach(() => {
// 각 테스트 이후 mock 정리
jest.clearAllMocks();
jest.restoreAllMocks();
});
test('파일에 있는 상품 목록 출력', async () => {
await run({
inputs: ['[콜라-1]', 'N', 'N'],
expected: [
/* prettier-ignore */
"- 콜라 1,000원 10개 탄산2+1",
'- 콜라 1,000원 10개',
'- 사이다 1,000원 8개 탄산2+1',
'- 사이다 1,000원 7개',
'- 오렌지주스 1,800원 9개 MD추천상품',
'- 오렌지주스 1,800원 재고 없음',
'- 탄산수 1,200원 5개 탄산2+1',
'- 탄산수 1,200원 재고 없음',
'- 물 500원 10개',
'- 비타민워터 1,500원 6개',
'- 감자칩 1,500원 5개 반짝할인',
'- 감자칩 1,500원 5개',
'- 초코바 1,200원 5개 MD추천상품',
'- 초코바 1,200원 5개',
'- 에너지바 2,000원 5개',
'- 정식도시락 6,400원 8개',
'- 컵라면 1,700원 1개 MD추천상품',
'- 컵라면 1,700원 10개',
],
});
});
test('여러 개의 일반 상품 구매', async () => {
await run({
inputs: ['[비타민워터-3],[물-2],[정식도시락-2]', 'N', 'N'],
expectedIgnoringWhiteSpaces: ['내실돈18,300'],
});
});
//
test('기간에 해당하지 않는 프로모션 적용', async () => {
mockNowDate('2024-02-01');
//
await run({
inputs: ['[감자칩-2]', 'N', 'N'],
expectedIgnoringWhiteSpaces: ['내실돈3,000'],
});
});
//
test('예외 테스트', async () => {
await runExceptions({
inputs: ['[컵라면-12]', 'N', 'N'],
inputsToTerminate: INPUTS_TO_TERMINATE,
expectedErrorMessage:
'[ERROR] 재고 수량을 초과하여 구매할 수 없습니다. 다시 입력해 주세요.',
});
});
});
참고 문서
https://spacebike.tistory.com/56
https://soobing.github.io/test/test-introduction/
https://inpa.tistory.com/entry/JEST-%F0%9F%93%9A-jest-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%A6%AC
https://www.daleseo.com/jest-fn-spy-on/
코딩앙마 유튜브
jest 공식문서