본문 바로가기
source-code/FrontEnd

[jest] mock.calls를 통한 mock 함수 실행 테스트 개선하기

by mattew4483 2024. 6. 4.
728x90
반응형

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 Functions · Jest

Mock functions allow you to test the links between code by erasing the actual implementation of a function, capturing calls to the function (and the parameters passed in those calls), capturing instances of constructor functions when instantiated with new,

jestjs.io

모든 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)
    })
}

 

다음과 같은 형태로 테스트 코드를 추가,

단순한 호출 여부뿐만 아니라 의도한 값과 함께 호출되었는지도 함께 테스트할 수 있게 되었습니다.

 

728x90
반응형