tag:crieit.net,2005:https://crieit.net/tags/Jest/feed 「Jest」の記事 - Crieit Crieitでタグ「Jest」に投稿された最近の記事 2022-04-23T01:06:29+09:00 https://crieit.net/tags/Jest/feed tag:crieit.net,2005:PublicArticle/18173 2022-04-23T01:06:29+09:00 2022-04-23T01:06:29+09:00 https://crieit.net/posts/jest-operate-dom-and-ignore-console-error-20220425 Jest で DOM 操作の試験、 console.error を無視 <p>Jest によるテストコードを書く上でいくつか嵌まったポイントをピックアップ。</p> <h2 id="DOM 操作の試験"><a href="#DOM+%E6%93%8D%E4%BD%9C%E3%81%AE%E8%A9%A6%E9%A8%93">DOM 操作の試験</a></h2> <p>まず引っかかったのは DOM 操作。今回はバリバリ DOM を操作したりある要素が存在しているかのチェックをする、といった完全に DOM ありきのコードを対象にしていました。</p> <p>機能のユニットテストであれば直感的に分かるのですが、 DOM 操作って……。</p> <h3 id="sample/assert.js"><a href="#sample%2Fassert.js">sample/assert.js</a></h3> <pre><code class="js">const dom = ` <div id="hoge"></div> `; module.exports = dom; </code></pre> <h3 id="sample/exception.js"><a href="#sample%2Fexception.js">sample/exception.js</a></h3> <pre><code class="js">const dom = ` <!--<div id="hoge"></div>--> `; module.exports = dom; </code></pre> <h3><strong>tests</strong>/hoge.test.js</h3> <pre><code class="js">const hoge = require('../src/hoge'); const domAssert = require('../sample/assert'); const domException = require('../sample/exception'); test('method:hoge test:assert', () => { document.body.innerHTML = domAssert; const hogeDOM = document.querySelector('#hoge'); const hogeInstance = new hoge(); expect(hogeInstance.judge(hogeDOM)).toBe(true); }); test('method:hoge test:exception', () => { document.body.innerHTML = domException; const hogeDOM = document.querySelector('#hoge'); const hogeInstance = new hoge(); expect(hogeInstance.judge(hogeDOM)).toBe(false); }); </code></pre> <p>例えば <code>hoge</code> という <code>id</code>属性 が付与された DOM が存在するか否かのチェックをする <code>hoge</code>クラス に対して <code>judge()</code>メソッド をテストしたい場合。</p> <ol> <li>DOM となるHTMLタグのコードを JS の文字列としてセット</li> <li>それを <code>module.exports</code></li> <li>テストコードで <code>require()</code> し、コード内の <code>document.body.innerHTML</code> へ代入する</li> <li>後は通常の JS のように <code>document.querySelector</code> なり何なりで操作やチェックが可能。属性であれば <code>getAttribute()</code> してあげれば良い</li> </ol> <p>……わりと素直にできることが分かりました。 Jest すごい。</p> <h3 id="The error below may be caused by using the wrong test environment"><a href="#The+error+below+may+be+caused+by+using+the+wrong+test+environment">The error below may be caused by using the wrong test environment</a></h3> <p>さて、上述でわりと直感的に DOM 操作した上でテストコードを記述できる、と記載しましたがいざテストをしようとすると以下のエラーが発生しました (Jest が 27系 以上)。</p> <blockquote> <p>The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string.<br /> Consider using the "jsdom" test environment.</p> </blockquote> <p>これは Jest に DOM 操作用の設定が必要なためのようです。</p> <p>解決策としては2つ。</p> <h4 id="テストコード内に設定を記述"><a href="#%E3%83%86%E3%82%B9%E3%83%88%E3%82%B3%E3%83%BC%E3%83%89%E5%86%85%E3%81%AB%E8%A8%AD%E5%AE%9A%E3%82%92%E8%A8%98%E8%BF%B0">テストコード内に設定を記述</a></h4> <p>まずはテストコード内に設定を記述する方法。これはテストコードごとに設定を変更できる柔軟性がある一方、必要な場合は全てのテストコードで同じ記述をする必要があるため数が多いと面倒になる、というパターン。</p> <pre><code class="js">/** * @jest-environment jsdom */ </code></pre> <h4 id="jest.config.js"><a href="#jest.config.js">jest.config.js</a></h4> <p>もう1つは <code>jest.config.js</code> に記述する方法。こちらは一括指定ができるものの、テストコードごとの設定はできないので先程とは逆ですね。</p> <pre><code class="js">module.exports = { testEnvironment: 'jsdom', // 追加 coverageDirectory: 'coverage' }; </code></pre> <h2 id="console.error() を無視"><a href="#console.error%28%29+%E3%82%92%E7%84%A1%E8%A6%96">console.error() を無視</a></h2> <p>テスト対象のコードで例外等の際に <code>console.error()</code> を使用していると、そのコードに入ったときに Jest が止まってしまいます。</p> <p>そこで、 <code>console.error()</code> に遭遇しても Jest が止まらないようにする方法を。</p> <pre><code class="js">const hoge = require('../src/hoge'); const domException = require('../sample/exception'); test('method:fuga test:exception', () => { jest.spyOn(console, 'error').mockImplementation((mes) => { console.log(mes); }); document.body.innerHTML = domException; const hogeDOM = document.querySelector('#hoge'); const hogeInstance = new hoge(); expect(hogeInstance.fuga(hogeDOM)).toBe(false); }); </code></pre> <p>例えばこんな感じ。 <code>jest.spyOn(console, 'error') ...</code> を実際のコードを走らせる前に記述しておくことで <code>console.error()</code> で止まらなくなります。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <h3 id="DOM操作"><a href="#DOM%E6%93%8D%E4%BD%9C">DOM操作</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://crudzoo.com/blog/jest">JestでjQuery主体のJavaScriptをテストする① | Crudzoo</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://jestjs.io/ja/docs/tutorial-jquery">DOM 操作 · Jest</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://tech.excite.co.jp/entry/2021/12/10/100000">DOM操作をするjQueryコードをJestでテストする方法を勉強した話 - エキサイト TechBlog.</a></li> </ul> <h3 id="The error below may be caused by using the wrong test environment"><a href="#The+error+below+may+be+caused+by+using+the+wrong+test+environment">The error below may be caused by using the wrong test environment</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/mame_daifuku/items/79b6a5a1514a3f067e1a">Jestの「 The error below may be caused by using the wrong test environment」の解決方法 - Qiita</a></li> </ul> <h3 id="undefined 判定"><a href="#undefined+%E5%88%A4%E5%AE%9A">undefined 判定</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://jestjs.io/ja/docs/expect#tobeundefined">Expect · Jest</a></li> </ul> <h3 id="null 判定"><a href="#null+%E5%88%A4%E5%AE%9A">null 判定</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://jestjs.io/ja/docs/expect#tobenull">Expect · Jest</a></li> </ul> <h3 id="(未使用) 非同期"><a href="#%28%E6%9C%AA%E4%BD%BF%E7%94%A8%29+%E9%9D%9E%E5%90%8C%E6%9C%9F">(未使用) 非同期</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://jestjs.io/ja/docs/asynchronous">非同期コードのテスト · Jest</a></li> </ul> <h3 id="console.errorで止まらないように"><a href="#console.error%E3%81%A7%E6%AD%A2%E3%81%BE%E3%82%89%E3%81%AA%E3%81%84%E3%82%88%E3%81%86%E3%81%AB">console.errorで止まらないように</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://zenn.dev/nus3/scraps/f1ea3cb4982593">Jest 逆引き テストケース</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://blog.kozakana.net/2021/02/dont-stop-with-console-error-when-running-tests-in-jest/">jestでテスト実行時console.errorで止まらないようにする | Simple is Beautiful.</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/manten120/items/790de8e1612fc8a15355">【Jest】console.log()をmock化する方法2通り - Qiita</a></li> </ul> arm-band tag:crieit.net,2005:PublicArticle/17627 2021-08-30T17:14:25+09:00 2021-08-30T17:14:25+09:00 https://crieit.net/posts/GitHub-Actions-Jest GitHub ActionsでJestを叩くときのタイムゾーンと言語を設定する <p>GitHub リポジトリにプッシュした時、GitHub Actions のワークフローで自動的に Jest を走らせるようにしています。GitHub 上で自動的に走る Jest と、手動で直接叩く Jest との違いはタイムゾーンとロケールです。ここでは、GitHub 上で Jest を動かすときに環境変数としてタイムゾーンとロケールを指定する方法について説明します。</p> <h2 id="1. package.json にグローバル設定ファイルを指定する"><a href="#1.+package.json+%E3%81%AB%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B">1. package.json にグローバル設定ファイルを指定する</a></h2> <p>以下のように設定を追記します。<code>jest-global-setup.js</code>に設定を記載すれば、環境変数から読み込まれた値を上書きできます。</p> <pre><code class="javascript"> "jest": { "globalSetup": "./jest-global-setup.js" }, </code></pre> <h2 id="2. jest-global-setup.js の中身を記載する"><a href="#2.+jest-global-setup.js+%E3%81%AE%E4%B8%AD%E8%BA%AB%E3%82%92%E8%A8%98%E8%BC%89%E3%81%99%E3%82%8B">2. jest-global-setup.js の中身を記載する</a></h2> <p>タイムゾーンと言語を指定します。</p> <pre><code class="javascript">module.exports = async () => { process.env.TZ = "Asia/Tokyo"; process.env.LANG = "en_US.UTF-8"; }; </code></pre> <h2 id="3. 動作結果を確認する"><a href="#3.+%E5%8B%95%E4%BD%9C%E7%B5%90%E6%9E%9C%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B">3. 動作結果を確認する</a></h2> <p>Jest のテストで出力される日時の文字列を確認したところ、言語が日本語から英語になっていることが確認できました。</p> <pre><code class="text"> - "date": "Wed Apr 01 2020 09:00:00 GMT+0900 (日本標準時)", + "date": "Wed Apr 01 2020 09:00:00 GMT+0900 (Japan Standard Time)", </code></pre> kabueye tag:crieit.net,2005:PublicArticle/16723 2021-03-08T01:51:05+09:00 2021-03-08T01:51:05+09:00 https://crieit.net/posts/jest-private-readonly-mock 📝 Jest で private readonly な値をモックする方法 <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>Jest でクラスの <code>private readonly</code> な変数を差し替えたい時に若干引っかかったのでメモっておきます。タイトルでは Jest とありますが、本記事の内容は JavaScript でモックする際の有効な手法の 1 つとして利用することが可能です。</p> <h1 id="Object.defineProperty を利用して値を差し替える"><a href="#Object.defineProperty+%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6%E5%80%A4%E3%82%92%E5%B7%AE%E3%81%97%E6%9B%BF%E3%81%88%E3%82%8B">Object.defineProperty を利用して値を差し替える</a></h1> <p>結論から言うと変数を差し替えたい場合は下記のような記述になります。</p> <pre><code class="javascript">const mockValue = ""; Object.defineProperty(service, "privateReadOnlyValue", { value: mockValue, }); </code></pre> <p>ちなみに関数を差し替えたい場合は下記のような記述になります。</p> <pre><code class="javascript">Object.defineProperty(service, "privateSumFunction", { value: jest.fn((a, b) => a + b), }); </code></pre> <p>各種テストケースで使いまわしているインスタンスの <code>private readonly</code> な変数をモックした場合、値をリストアしたいケースも出てきました。その場合の記述としては、下記が有効でした。</p> <pre><code class="javascript">// tmpService 変数に service インスタンスを clone して利用する const tmpService = Object.create(service); Object.defineProperty(tmpService, "privateReadOnlyValue", { value: "", }); </code></pre> <h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1> <p><code>Object.defineProperty</code> と <code>Object.create</code> を駆使すれば大体のケースでは事足りそうです。</p> <h1 id="参考リンク"><a href="#%E5%8F%82%E8%80%83%E3%83%AA%E3%83%B3%E3%82%AF">参考リンク</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/create">Object.create() - JavaScript | MDN</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/facebook/jest/issues/2227#issuecomment-265005782">Mocking read-only properties for a class · Issue #2227 · facebook/jest</a></li> </ul> nikaera tag:crieit.net,2005:PublicArticle/16230 2020-11-22T13:54:50+09:00 2020-11-22T13:54:50+09:00 https://crieit.net/posts/jest-test Jest を触ってみる <h2 id="経緯"><a href="#%E7%B5%8C%E7%B7%AF">経緯</a></h2> <p>個人的にはどうしても後回しにされがちなテストコードについて、少しでも精神的な障壁を下げるべく Jest を軽く触ってみることにしました。</p> <p>PHPUnit については<a target="_blank" rel="nofollow noopener" href="https://labor.ewigleere.net/2019/08/15/phpunit-coverage-output/">PHPUnitとXdebugでコードカバレッジの出力を試す</a>で触ったことがあるので、ユニットテストの雰囲気は掴んでいます。</p> <p>そこで今回は JS 側を……ということで Jest を触ってみることにします。</p> <p>今回は本当に最初の最初だけ。基本的には<a target="_blank" rel="nofollow noopener" href="https://app.codegrid.net/entry/2018-jest-1">Jestで始める! ユニットテスト - 環境の準備とテストの実行 | CodeGrid</a>の記事の通りに進めます。</p> <h2 id="準備"><a href="#%E6%BA%96%E5%82%99">準備</a></h2> <h3 id="package.json"><a href="#package.json">package.json</a></h3> <p>```json:package.json<br /> {<br /> "name": "finality_jest",<br /> "version": "0.0.1",<br /> "description": "jestのテスト",<br /> "main": "sum.js",<br /> "scripts": {<br /> "test": "jest",<br /> "test:coverage": "jest --coverage"<br /> },<br /> "author": "",<br /> "license": "ISC",<br /> "devDependencies": {<br /> "jest": "^26.6.3"<br /> }<br /> }</p> <pre><code><br />`npm init` した後 `yarn add --dev jest` で Jest を追加。 npm scripts にテスト実行とカバレッジ出力のスクリプトを追記。 ### sum.js ```javascript:sum.js const sum = (a, b) => { return a + b; }; module.exports = sum; </code></pre> <p>ほぼ記事のママです。</p> <h3 id="jest.config.js"><a href="#jest.config.js">jest.config.js</a></h3> <p>```javascript:jest.config.js<br /> module.exports = {<br /> coverageDirectory: 'coverage'<br /> };</p> <pre><code><br />現状では有効なオプションを指定していませんが、 `coverages` に変更してカバレッジ出力されるディレクトリが変化したことは確認できたので、後はテストケースに併せてこの辺(英: [Configuring Jest · Jest](https://jestjs.io/docs/en/configuration), 日: [Configuring Jest · Jest](https://jestjs.io/docs/ja/configuration))のオプションをカスタマイズしていけば良いのかな、と思います。 ### __test__/sum.test.js ```javascript:sum.test.js const sum = require('../sum'); test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); </code></pre> <p>こちらもほぼママ。実際に使用する場合はテストコードが増えていくと思われるので予めディレクトリに入れておく方向性で考えました。</p> <p>その場合、ファイル名末尾を <code>.test.js</code> にする必要はないのですが、実際のコードと同じ名前にすると、今回は <code>sum.js</code> と <code>__test__/sum.js</code> の区別がエディタ上で付きづらいので、結局 <code>sum.test.js</code> と末尾に <code>.test.js</code> を付ける方向の方が良いのかな、と感じました。</p> <h3 id="ディレクトリ構造"><a href="#%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E9%80%A0">ディレクトリ構造</a></h3> <p>以上を踏まえてディレクトリ構造は以下のようになりました。</p> <pre><code> / ├ __test__/ │ └ sum.test.js ├ .gitignore ├ jest.config.js ├ package.json └ sum.js </code></pre> <h2 id="実行結果"><a href="#%E5%AE%9F%E8%A1%8C%E7%B5%90%E6%9E%9C">実行結果</a></h2> <p>これでテストを実行。</p> <pre><code class="bash">> yarn test:coverage $ jest --coverage PASS __tests__/sum.test.js √ adds 1 + 2 to equal 3 (3 ms) ----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------|---------|----------|---------|---------|------------------- All files | 100 | 100 | 100 | 100 | sum.js | 100 | 100 | 100 | 100 | ----------|---------|----------|---------|---------|------------------- Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 4.061 s Ran all test suites. Done in 6.86s. </code></pre> <p>OKですね。</p> <p>試しに <code>sum.js</code> を以下のように変更します。</p> <p>```javascript:sum.js<br /> const sum = (a, b) => {<br /> return a - b;<br /> };</p> <p>module.exports = sum;</p> <pre><code><br />加算ではなく減算にしました。これで再度テスト実行。 ```bash > yarn test:coverage yarn run v1.22.4 $ jest --coverage FAIL __tests__/sum.test.js × adds 1 + 2 to equal 3 (6 ms) ● adds 1 + 2 to equal 3 expect(received).toBe(expected) // Object.is equality Expected: 3 Received: -1 2 | 3 | test('adds 1 + 2 to equal 3', () => { > 4 | expect(sum(1, 2)).toBe(3); | ^ 5 | }); 6 | at Object.<anonymous> (__tests__/sum.test.js:4:23) ----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------|---------|----------|---------|---------|------------------- All files | 100 | 100 | 100 | 100 | sum.js | 100 | 100 | 100 | 100 | ----------|---------|----------|---------|---------|------------------- Test Suites: 1 failed, 1 total Tests: 1 failed, 1 total Snapshots: 0 total Time: 4.363 s Ran all test suites. error Command failed with exit code 1. </code></pre> <p>怒られますね。OKです。</p> <p>元に戻して再度実行。</p> <p>今度は生成された <code>coverage</code> ディレクトリを辿ります。 <code>coverage/lcov-report/</code> を開くと以下のようにカバレッジが出力されていました。</p> <p><a href="https://crieit.now.sh/upload_images/c33578e905758a85b4f40a0e6b13bd2d5fb9ef05c5be5.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c33578e905758a85b4f40a0e6b13bd2d5fb9ef05c5be5.jpg?mw=700" alt="テストプロジェクトのカバレッジ出力" /></a></p> <p>参考記事の通り、パッケージ一つでここまでできると助かりますね。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li>英: <a target="_blank" rel="nofollow noopener" href="https://jestjs.io/docs/en/configuration">Configuring Jest · Jest</a></li> <li>日: <a target="_blank" rel="nofollow noopener" href="https://jestjs.io/docs/ja/configuration">Configuring Jest · Jest</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://app.codegrid.net/entry/2018-jest-1">Jestで始める! ユニットテスト - 環境の準備とテストの実行 | CodeGrid</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/hogesuke_1/items/8da7b63ff1d420b4253f">この頃流行りのJestを導入して軽快にJSをテストしよう - Qiita</a></li> </ul> arm-band