[번역] Jest Mocks에 대한 이해
Tags: javascript, jest, testing, mocking
Published: 2021-02-22 02:50:48

Jest의 Mock 함수를 구현하는 3가지 방법에 대한 설명을 번역한 글입니다. 어느 상황에 어떤 Mock 함수를 써야할지 몰라서 헤매던 와중에 발견했습니다. 원문에 달려있는 `공식문서보다 이해하기 잘 쓰여진 글이다` 라는 댓글에 백번 공감하며 번역해보았습니다.
원문은 https://medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c 에서 확인하실 수 있습니다.
틀린 부분은 편하게 댓글 달아주시면 감사하겠습니다~!


---

Mocking은 테스트를 독립시키기 위해 의존성을 개발자가 컨트롤하고 검사할 수 있는 오브젝트로 변환하는 테크닉입니다. 의존성은 무엇이든 될 수 있지만, 일반적으로 import 하는 모듈입니다.
자바스크립트에는 [testdouble](https://github.com/testdouble/testdouble.js) 과 [sinon](https://sinonjs.org/)처럼 훌륭한 mocking 라이브러리가 있고, Jest는 기본적으로 제공하는 기능입니다.
최근에 저는 [Jest의 이슈 트래커를 돕기위해](https://github.com/facebook/jest/issues?utf8=%E2%9C%93&q=involves%3Arickhanlonii)  Collaborator로서 Jest팀에 참여했습니다. 거기서 많은 이슈들이 Jest에서 [어떻게 mocking 하는지에 관한 질문](https://github.com/facebook/jest/issues?utf8=%E2%9C%93&q=how+mock+)이라는 것을 깨닫고, 이 것들을 한번에 설명하는 가이드를 작성하기로 했습니다.
우리가 Jest에서 Mocking을 이야기할 때, 일반적으로 의존성을 [Mock Function](https://jestjs.io/docs/en/mock-function-api.html)으로 대체하는 것에 대해 말합니다. 이 글에서 Mock 함수에 대해 리뷰해보고, 의존성을 대체하는 여러가지 방법으로 deep dive 해보겠습니다.
<br />
## The Mock Function ##
Mocking의 목적은 우리가 컨트롤할 수 없는 무엇인가를 대체하는 것이기 때문에, 우리가 대체하는 것이 필요로하는 모든 기능을 갖고 있는게 중요합니다.
Mock 함수는 다음 기능을 제공합니다.
- 함수호출 Capture
- Return Value 설정
- 구현 변경하기

Mock 함수 인스턴스를 만드는 가장 간단한 방법은 `jest.fn()` 을 쓰는 것입니다.
이 것과 [Jest Expect](https://jestjs.io/docs/en/expect.html)를 쓰면, 함수호출을 Capture해서 쉽게 테스트할 수 있습니다.

{% gist https://gist.github.com/rickhanlonii/2bdc004d56e329fa149bcf9eeafa75cd %}

그리고 Return Value, 구현, Promise Resolution을 바꿀 수도 있습니다.

{% gist https://gist.github.com/rickhanlonii/dcbdc0f10a82646fba474711e9a13193 %}

Mock 함수가 무엇이고 이 것으로 무엇을 할 수 있는지 알아봤습니다. 이제 어떻게 사용할지 알아봅시다.
<br />
## 의존성 주입 ##

Mock 함수를 사용하는 일반적인 방법 중 하나는 테스트하려는 함수로 arguments를 직접 전달하는 방식입니다. 이 것은 테스트를 실행시키고, Mock 함수가 어떤 arguments와 어떻게 실행됐는지 assert구문으로 확인해 볼 수 있습니다.

{% gist https://gist.github.com/rickhanlonii/b6ac9092cd35b11bf811a5fb8ff21f99 %}

이 전략은 견고한 테스트를 만들지만 테스트코드가 의존성주입을 허용하도록 요구합니다. 종종 그럴 수 없는 경우에, 우리는 실제로 존재하는 모듈과 함수를 Mocking 해야 합니다.
<br />
## 모듈과 함수를 Mocking하기 ##
Jest에서 모듈과 함수를 Mocking 하는 3가지 방법이 있습니다.
- jest.fn: Mock a function
- jest.mock: Mock a module
- jest.spyOn: Spy or mock a function

이 것들은 각각의 방식으로 Mock 함수를 만드는데, 어떻게 동작하는지 설명을 하기 위해 다음과 같은 폴더구조로 만들어 보겠습니다.

{% gist https://gist.github.com/rickhanlonii/f694c631cd3d2ec01e553302e4a64406 %}

이 설정에서는 `math.js` 함수를 실제로 호출하지 않고 `app.js`를 테스트하면서, 함수가 예상대로 호출되는지 확인하기 위해 Spy를 하는 것이 일반적입니다. 예시들은 진부하지만 `math.js` 의 함수들이 복잡한 계산을 하거나 개발자가 피하고싶은 IO를 만드는 요청이라고 상상해주세요.

{% gist https://gist.github.com/rickhanlonii/49709d973a8ac680c94290c8fee471a5 %}

{% gist https://gist.github.com/rickhanlonii/b1133ae01d661250caf36377d816f0b5 %}  

## jest.fn으로 Mocking 하기 ##
가장 기본적인 전략은 함수를 Mock 함수로 재할당하는 것입니다. 재할당된 함수가 쓰이는 어디서든지 Mock 함수가 원래의 함수 대신 호출될 것입니다.

{% gist https://gist.github.com/rickhanlonii/ee1e73da7e1db0e3dbc1bbf812bb093f %}

이렇게 Mocking 하는 방식은 몇 가지 이유로 덜 쓰입니다.  
- `jest.mock` 은 자동적으로 모듈의 모든 함수를 Mocking 해줍니다.
- `jest.spyOn` 도 마찬가지로 모든 함수를 Mocking 해주면서 원래의 함수를 다시 복원할 수도 있습니다.  

## jest.mock으로 Mocking 하기 ##
좀 더 일반적인 접근법은 자동적으로 모듈이 exports하는 모든 것들을 Mocking 해주는 `jest.mock` 을 쓰는 것입니다. 따라서 `jest.mock('./math.js')` 를 해주면 본질적으로 `math.js` 를 다음처럼 설정하는 것입니다.

{% gist https://gist.github.com/rickhanlonii/da2f8ffaeb5bfe754c4da46e07dc8d14 %}

여기서부터 모듈이 exports 하는 모든 것들에 Mock 함수 기능을 쓸 수 있습니다.

{% gist https://gist.github.com/rickhanlonii/bbdc87e4d8b26d0c13fb1715635ffb6f %}

이것은 가장 쉽고 일반적인 Mocking 방법입니다. (Jest의 `automock: true` 설정 방식이기도 합니다)
이 전략의 유일한 단점은 모듈의 원래 구현에 접근하기 어렵다는 것입니다. 이런 경우를 대비해 `spyOn` 이 있습니다.  
## jest.spyOn으로 Spy 혹은 Mocking하기 ##
때로 우리는 메소드가 실행되는 것을 지켜보길 원할뿐만 아니라, 기존의 구현은 보존하길 바랍니다. 구현을 Mocking하고 차후에 테스트구문에서 원본을 복원할 수 있습니다.
이 경우에 `jest.spyOn` 을 쓸 수 있습니다.
단순히 math 함수에 "Spy"를 호출하고 원본 구현은 그대로 둘 수 있습니다.

{% gist https://gist.github.com/rickhanlonii/6e6c634daaf30a32716a19edf71f8bee %}

이것은 실제로 함수를 대체하지 않고, 특정한 사이드 이펙트가 발생하는지 테스트하는 몇몇 시나리오에 유용합니다.
함수를 Mocking하고 다시 원래 구현을 복원할 수도 있습니다.

{% gist https://gist.github.com/rickhanlonii/4195b5a132f8c7bc47ba147cdcba1c05 %}

Jest는 각각의 테스트 파일이 샌드박스화 되어 있기 때문에, `afterAll` 훅을 불필요하게 사용하지 않도록 하는 경우에 유용합니다.
`jest.spyOn` 는 기본적으로 `jest.fn()` 의 사용에 대한 Sugar(일반적으로 말하는 Syntactic Sugar를 말하는 것 같습니다: 역자 주)라는 것이 키포인트 입니다. 우리는 기존의 구현을 저장하고, Mocking 했다가, 기존 구현을 재할당하는 방식으로 똑같은 목표를 달성할 수 있습니다.

{% gist https://gist.github.com/rickhanlonii/c695cbc51ae6ffd81c46f46509171650 %}

이 것이 실제로 `jest.spyOn` 이 [구현된 방식](https://github.com/facebook/jest/blob/e9aa321e0587d0990bd2b5ca5065e84a1aecb2fa/packages/jest-mock/src/index.js#L674-L708)입니다.  
## 결론 ##
이 글에서 우리는 Mock 함수가 무엇인지와 모듈과 함수 호출을 트래킹하고, 구현과 return value를 바꾸는 방법을 배웠습니다.
저는 여러분이 Jest Mock을 쉽게 이해하고 고통없이 테스트를 작성하는데 더 많은 시간을 쓸 수 있도록 돕기를 바랍니다. Mocking에 대한 더 많은 정보와 best practice들은 [Justin Searls](https://medium.com/@searls)에 의해 [Don't Mock Me](https://www.youtube.com/watch?v=Af4M8GMoxi4) 라고 이름 붙여진 발표와 700장이 넘는 슬라이드를 확인해보세요.
트위터와 스택오버플로우, 디스코드 채널로 무엇이든 물어보세요.