{
    "componentChunkName": "component---src-templates-blog-post-js",
    "path": "/21-10-29-firebase-emulator-test/",
    "result": {"data":{"site":{"siteMetadata":{"title":"irrationnelle","author":"irrationnelle","siteUrl":"https://dev.rase.blog","comment":{"disqusShortName":"","utterances":"irrationnelle/devlog"},"sponsor":{"buyMeACoffeeId":""}}},"markdownRemark":{"id":"fbae4dca-3d69-5c30-ac96-b9c06bcdbe75","excerpt":"tdd 로 react 앱을 만들다가 firebase 구현 단계까지 왔다. 로그인 버튼을 클릭했을 때 signIn 함수를 호출하는 것까지 테스트하고 있으나 signIn 함수 내부를 테스트 하는 것에 대하여 고민이 좀 있었다. signIn 내부 함수를 유닛 테스트로 다루는 것이 맞는지, 아니면 signIn 함수 내부를 내부 구현 단계로 보고 인터페이스 역할을 하는 signIn 함수까지만 통합 테스트에서 다룰 것인지 api 호출을 mocking 하거나 msw…","html":"<p>tdd 로 react 앱을 만들다가 firebase 구현 단계까지 왔다.</p>\n<p>로그인 버튼을 클릭했을 때 signIn 함수를 호출하는 것까지 테스트하고 있으나</p>\n<p>signIn 함수 내부를 테스트 하는 것에 대하여 고민이 좀 있었다.</p>\n<p>signIn 내부 함수를 유닛 테스트로 다루는 것이 맞는지,</p>\n<p>아니면 signIn 함수 내부를 내부 구현 단계로 보고 인터페이스 역할을 하는 signIn 함수까지만 통합 테스트에서 다룰 것인지</p>\n<p>api 호출을 mocking 하거나 msw 같은 걸 이용해서 가짜 응답을 주는 것으로 signIn 정도 단위의 operation 도 테스트하는 것을 어렵잖게 볼 수 있어서 일단은 유닛 테스트를 작성해보기로 했다.</p>\n<p>사실 이미 작성한 통합 테스트에서는 signIn 함수 호출 여부만 확인하고 있어서, 함수 호출 성공 상황이나 실패 상황에서 그려내야 하는 스토리에 따라 자연스럽게 api 호출을 mocking 할 수도 있겠지만</p>\n<p>일단은 유닛 테스트로 작성해본다. 통합 테스트 단계에서 많이 겹치면 그때가서 합치자.</p>\n<h2 id=\"firebase-emulator\" style=\"position:relative;\"><a href=\"#firebase-emulator\" aria-label=\"firebase emulator permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>firebase emulator</h2>\n<p>firebase 는 이런 유닛 테스트나 실제 운용하는 파이어베이스에 영향을 주지 않고도 자신들의 서비스가 어떻게 작동하는지 확인할 수 있도록 에뮬레이터를 제공한다.</p>\n<p>나는 이 포스팅에서 auth 에뮬레이터만 사용할 예정이지만, firestore 등도 에뮬레이터가 있다. 사실 auth 보다 이쪽이 더 실용성이 높을 듯.</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> signIn <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"./auth\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> firebaseConfig <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"../constant/firebase-config\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> initializeApp <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"firebase/app\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span>\n  getAuth<span class=\"token punctuation\">,</span>\n  signInWithEmailAndPassword<span class=\"token punctuation\">,</span>\n  connectAuthEmulator<span class=\"token punctuation\">,</span>\n  UserCredential\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"firebase/auth\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> app <span class=\"token operator\">=</span> <span class=\"token function\">initializeApp</span><span class=\"token punctuation\">(</span>firebaseConfig<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> auth <span class=\"token operator\">=</span> <span class=\"token function\">getAuth</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token function\">connectAuthEmulator</span><span class=\"token punctuation\">(</span>auth<span class=\"token punctuation\">,</span> <span class=\"token string\">\"http://localhost:9099\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token function\">test</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Example test case\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> cred<span class=\"token operator\">:</span> UserCredential <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">signInWithEmailAndPassword</span><span class=\"token punctuation\">(</span>\n      auth<span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"foo@bar.de\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"bla2377\"</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span>cred<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toBeTruthy</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span>cred<span class=\"token punctuation\">.</span>user<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toBeTruthy</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token builtin\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">throw</span> e<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>위 코드만 보면 별거 아니지만 firebase 가 워낙 alias 가 많아서 위 코드와 완전히 동일한 예제를 만들기는 조금 힘들었다.</p>\n<p>위 테스트 코드는 두 가지 이유로 실패한다.</p>\n<ol>\n<li>에뮬레이터를 실행하지 않았다.</li>\n<li>예시로 든 계정은 존재하지 않는다.</li>\n</ol>\n<p>먼저 에뮬레이터를 실행하자.</p>\n<h1 id=\"firebase-에뮬레이터-실행\" style=\"position:relative;\"><a href=\"#firebase-%EC%97%90%EB%AE%AC%EB%A0%88%EC%9D%B4%ED%84%B0-%EC%8B%A4%ED%96%89\" aria-label=\"firebase 에뮬레이터 실행 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>firebase 에뮬레이터 실행</h1>\n<p>먼저 <code class=\"language-text\">firebase-tools</code> 를 전역으로 설치한다.</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">$ <span class=\"token function\">npm</span> i -g firebase-tools</code></pre></div>\n<p>이제 다음 명령어로 에뮬레이터를 실행하자.</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">$ firebase emulators:start</code></pre></div>\n<p>정말 간단한 방법이라고 생각했는데 실행이 안 된다. 다음과 같은 에러가 발생한다.</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">Error: Not <span class=\"token keyword\">in</span> a Firebase app directory <span class=\"token punctuation\">(</span>could not <span class=\"token function\">locate</span> firebase.json<span class=\"token punctuation\">)</span></code></pre></div>\n<p>firebase.json 을 가장 쉽게 생성하는 방법은 <code class=\"language-text\">firebase init</code> 을 실행하는 것이다.</p>\n<p>그리고 Emulators 를 선택하고 에뮬레이터를 돌릴 프로젝트를 선택한 뒤, 원하는 emulator 를 선택한다.</p>\n<p>그럼 에뮬레이터를 실행할 수 있다.</p>\n<h1 id=\"유닛-테스트와-에뮬레이터를-동시-실행\" style=\"position:relative;\"><a href=\"#%EC%9C%A0%EB%8B%9B-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-%EC%97%90%EB%AE%AC%EB%A0%88%EC%9D%B4%ED%84%B0%EB%A5%BC-%EB%8F%99%EC%8B%9C-%EC%8B%A4%ED%96%89\" aria-label=\"유닛 테스트와 에뮬레이터를 동시 실행 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>유닛 테스트와 에뮬레이터를 동시 실행</h1>\n<p>일반적인 환경에서는 shell 을 여러 개 띄우는 것이 어렵지 않기 때문에 에뮬레이터를 띄운 뒤 다른 shell 에서 테스트를 실행할 수 있다.</p>\n<p>하지만 나처럼 아이패드 환경에서 외부 서버에 있는 ubuntu 에 연결하는 경우에는 shell 을 여러개 띄우기가 어렵다. 완전히 방법은 없는 건 아니지만 그래도 shell 하나로 해결하고 싶을 때 요긴하게 사용할 수 있는 명령어가 있다.</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">$ firebase emulators:exec ‘npm run test’</code></pre></div>\n<p>이러면 에뮬레이터를 실행하고 그 후 이어서 유닛테스트를 실행하기 때문에 유닛 테스트를 에뮬레이터 위에서 돌려볼 수 있다.</p>\n<h1 id=\"존재하지-않는-계정-에러\" style=\"position:relative;\"><a href=\"#%EC%A1%B4%EC%9E%AC%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EA%B3%84%EC%A0%95-%EC%97%90%EB%9F%AC\" aria-label=\"존재하지 않는 계정 에러 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>존재하지 않는 계정 에러</h1>\n<p>하지만 여전히 위의 테스트는 실행하면 실패한다.</p>\n<p><code class=\"language-text\">signInWithEmailAndPassword</code> 는 로그인 때 사용하는 함수인데, 인자로 전달한 이메일이 존재하지 않기 때문이다.</p>\n<p>그래서 회원가입이 선행되어야 한다. 회원가입은 firebase 의 <code class=\"language-text\">createUserWithEmailAndPassword</code> 함수 호출로 가능하지만, 이걸 매번 호출하면 이번에는 존재하는 아이디로 가입을 시도한다고 또 테스트가 실패한다.</p>\n<p>그래서 테스트 코드임에도 불구하고 예외처리 과정을 추가해야 한다. beforeAll 에 선언해서 test suite 에서 단 1회만 실행하도록 할 수는 있지만, <code class=\"language-text\">—watch</code> 옵션으로 코드 변경 때마다 테스트의 피드백을 받고 싶은 경우에는 어쩔 수 없이 예외처리를 해줄 수 밖에 없다.</p>\n<p>아니면 방법이 하나 더 있는데, 가입 때 사용하는 이메일 계정을 random 하게 생성한 뒤, 두 테스트에서 이 이메일 계정을 사용하는 것이다. 이 경우 항상 random 한 이메일 계정으로 생성과 로그인을 반복하기 때문에 예외처리가 필요없다.</p>\n<p>이 대안에도 아쉬운 점은 존재한다.  <code class=\"language-text\">only</code> 나 <code class=\"language-text\">skip</code>  키워드로 특정 테스트만 실행할 수 없다는 점이다. 반면 예외처리를 진행한 경우에는 이 키워드를 여전히 사용할 수 있다.</p>","frontmatter":{"title":"firebase 에뮬레이터로 테스트 코드 작성하기","date":"October 29, 2021"}}},"pageContext":{"slug":"/21-10-29-firebase-emulator-test/","previous":{"fields":{"slug":"/21-10-19-event-loop-raf/"},"frontmatter":{"title":"event loop 와 requestAnimationFrame","category":"dev","draft":false}},"next":{"fields":{"slug":"/21-11-09-vanilla-javascript-input-and-test/"},"frontmatter":{"title":"vanilla javascript 로 키입력마다 input 이벤트 발생시키기 그리고 테스트","category":"dev","draft":false}}}},
    "staticQueryHashes": ["3128451518","3474900066"]}