Backgrounds
주입한 모듈 메서드의 실행 여부를 테스트해야 할 때가 있습니다.
주로 실제 구현은 사용처에서 이뤄지고, 단위 테스트에서는 인터페이스에만 의존하는 모듈을 테스트할 때 발생하죠.
예를 들어 주문을 생성하고, 알람을 띄우는 함수를 테스트할 수 있습니다.
// makeOrder.test.ts
import makeOrder from './makeOrder.ts'
describe('주문 생성 use case 테스트', () => {
it('주문 생성 시, 해당 제품의 주문 완료 알람이 뜬다', () => {
})
}
해당 함수는 다음과 같이 구현할 수 있습니다.
// makeOrder.ts
const makeOrder = (product:Product, notification:NotifyService) => {
// 1. 주문 생성
// (...)
// 2. 생성된 주문으로 알림 호출
notification.nofify(product.name)
}
// notification.ts
// 알림 전송 모듈의 인터페이스를 정의합니다
interface NotifyService {
nofify(text:string):void
}
makeOrder 함수는 NotifyService 인터페이스에만 의존하고 있으며
이 덕분에 해당 함수를 테스트할 때는 '정말로 알람이 뜨는지'와 같은 부분은 신경 쓰지 않을 수 있습니다.
→ NotifyService 인터페이스의 nofity 메서드를 잘 호출하고 있는지 만 확인하면 되겠죠!
(nofity를 테스트 코드에서 직접 구현하지 않아도 되는 것이죠!)
// makeOrder.test.ts
import makeOrder from './makeOrder.ts'
describe('주문 생성 use case 테스트', () => {
it('주문 생성 시, 해당 제품의 주문 완료 알람이 뜬다', () => {
const mockNotify = jest.fn()
const mockNotificatioin: notifyService = {
notify: mockNotify
}
const product:Product = {}
makeOrder(product, mockNotificatioin)
// mockNotify가 호출되었는지 확인
expect(mockNotify).toHaveBeenCalled()
})
}
단위 테스트에서 notifyService 인터페이스를 구현해 주입해준 모습!
이때 jest.fn()을 통해 jest의 mock function을 사용해 줬고,
해당 모의 함수의 호출 여부를 toHaveBeenCalled로 확인할 수 있습니다.
Problems
물론 위 테스트만으로도 충분할 수 있지만... 그렇지 않을 때도 존재합니다.
→ 단순 호출 여부뿐만 아니라, 해당 함수가 어떻게 호출되는지, 무엇을 반환하는지 등을 테스트해야 할 경우겠죠!
예를 들어, 위 예시에서는 notify 함수는 알람에 띄울 문구(text)를 인자로 받고 있으며
makeOrder를 호출했을 때, 제품의 이름이 알림에 뜨는지를 테스트할 필요가 있습니다.
// makeOrder.test.ts
import makeOrder from './makeOrder.ts'
describe('주문 생성 use case 테스트', () => {
it('주문 생성 시, 해당 제품 이름으로 주문 완료 알람이 뜬다', () => {
const mockNotify = jest.fn()
const mockNotificatioin: notifyService = {
notify: mockNotify
}
const productName = 'product name'
const product:Product = {name: productName}
makeOrder(product, mockNotificatioin)
// mockNotify가 product의 name과 호출되었는지 확인 필요
expect(mockNotify).toHaveBeenCalled()
})
}
Solutions
이러한 상황에서 mock 함수의 .mock 속성을 사용할 수 있습니다.
https://jestjs.io/docs/mock-functions
모든 mock 함수는 .mock property를 갖고 있으며
해당 속성에는 함수가 어떻게 호출되고, 인스턴스화되고, 무엇을 반환하는지가 포함됩니다.
// The function was called exactly once
expect(someMockFunction.mock.calls).toHaveLength(1);
// The first arg of the first call to the function was 'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
// The second arg of the first call to the function was 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');
// The return value of the first call to the function was 'return value'
expect(someMockFunction.mock.results[0].value).toBe('return value');
// The function was called with a certain `this` context: the `element` object.
expect(someMockFunction.mock.contexts[0]).toBe(element);
// This function was instantiated exactly twice
expect(someMockFunction.mock.instances.length).toBe(2);
// The object returned by the first instantiation of this function
// had a `name` property whose value was set to 'test'
expect(someMockFunction.mock.instances[0].name).toBe('test');
// The first argument of the last call to the function was 'test'
expect(someMockFunction.mock.lastCall[0]).toBe('test');
공식 문서에서 다양한 속성들과 예시를 찾아볼 수 있습니다.
위 예시의 경우, mockNotify가 product의 name과 호출되었는지 테스트해야 하므로
→ mock.calls 속성을 사용하면 되겠죠!
[
[arg1_call1, arg2_call1, ...], // 첫 번째 호출의 인수 배열
[arg1_call2, arg2_call2, ...] // 두 번째 호출의 인수 배열
]
mock.calls는 위와 같은 구조를 가지므로
// makeOrder.test.ts
import makeOrder from './makeOrder.ts'
describe('주문 생성 use case 테스트', () => {
it('주문 생성 시, 해당 제품 이름으로 주문 완료 알람이 뜬다', () => {
const mockNotify = jest.fn()
const mockNotificatioin: notifyService = {
notify: mockNotify
}
const productName = 'product name'
const product:Product = {name: productName}
makeOrder(product, mockNotificatioin)
// mockNotify가 호출되었는지 확인
expect(mockNotify).toHaveBeenCalled()
// mockNotify의 첫번째 호출의 첫번째 인자가 productName인지 확인
expect(mockNotify.mock.calls[0][0]).toBe(productName)
})
}
다음과 같은 형태로 테스트 코드를 추가,
단순한 호출 여부뿐만 아니라 의도한 값과 함께 호출되었는지도 함께 테스트할 수 있게 되었습니다.
'source-code > FrontEnd' 카테고리의 다른 글
[jest] test.each를 통해 여러 입력값 테스트 간결하게 구현하기 (0) | 2024.06.13 |
---|---|
내가 프론트엔드에 클린 아키텍처를 도입한 이유 (0) | 2024.06.12 |
[jest] spyOn 사용 시 Cannot redefine property 에러 해결하기 (0) | 2024.05.30 |
[chrome extension] 에러 핸들링 (0) | 2024.02.02 |
[chrome extension] side panel 사용하기 (0) | 2024.02.01 |