78101


下図（図1）のソースコードを3つのテストメソッドからなるテストスイートでカバレッジを取ったところ図1のように漏れがありました。1番目のテストケースを単体で実行してもカバレッジは図1のようになります。

**図1: カバレッジに漏れがあるソースコード**

[![図1　カバレッジに漏れがあるソースコード](https://i.stack.imgur.com/164eW.png)](https://i.stack.imgur.com/164eW.png)

しかし、2番目のテストケースを単体で実行するとカバレッジは下図（図2）のようになります。

**図2: 2番目のテストケース単体でのカバレッジ**

[![図2　2番目のテストケース単体でのカバレッジ](https://i.stack.imgur.com/z6yl1.png)](https://i.stack.imgur.com/z6yl1.png)

また、テストケース3を単体で実行すると下図（図3）のようになります。

**図3: 3番目のテストケース単体でのカバレッジ**

[![図3　3番目のテストケース単体でのカバレッジ](https://i.stack.imgur.com/yqgng.png)](https://i.stack.imgur.com/yqgng.png)

これらの図の結果を合わせると網羅率が100%になると思うのですが、npm
testコマンドを実行して3つのテストをいっぺんに走らせても図1のようなカバレッジになり、100%にはなりません。100％にするための解決策を教えてください。  
JestもJavaScriptもあまり慣れていないので、うっかりミスもあるかもしれませんがよろしくお願いします。

ソースコード（js\callbackQuestion.js）

[code]

       import { handleErrors } from './fetchErrHandler.js'
    
    export default async function innerCallback(event) {
        event.preventDefault()
        const yearfield = document.getElementById('id_year')
        const monthfield = document.getElementById('id_month')
        const url = '/year/month/';
        const csrfmiddlewaretoken = "HueNL6M9N0bQhj02V64dj7LudpKICZhQR8vrSuBP1aMe25";
        var data = { year: yearfield.value, month: monthfield.value }
        try {
            const response = await fetch(url, {
                method: 'post',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRFToken': csrfmiddlewaretoken
                },
                body: JSON.stringify(data)
            }).catch(e => {
                throw new Error(e);
            });
            handleErrors(response);
            let json = response.json();
        } catch (e) {
            console.log('error:' + e);
        }
    }
    
[/code]

  
js\fetchErrHandler.js

[code]

        export const handleErrors = res => {
        if (!res.ok) {
          switch (res.status) {
            case 400:
              throw Error ('INVALID_TOKEN');
            case 401:
              throw Error ('UNAUTHORIZED');
            case 500:
              throw Error ('INTERNAL_SERVER_ERROR');
            case 502:
              throw Error ('BAD_GATEWAY');
            case 404:
              throw Error ('NOT_FOUND');
            default:
              throw Error ('UNHANDLED_ERROR');
          }
        }
        else {  // res.ok == true
          return res;
        }
    };
    
[/code]

テストファイル（js\ __tests__\coverageQuestions.test.js）

[code]

    import innerCallbackFunction from "../callbackQuestion";
    
    describe("click event", () => {
        beforeEach(() => {
            // レンダリングされたstaffs.htmlを読み込む
            const fs = require("fs");
            document.body.innerHTML 
                = fs.readFileSync(__dirname + "/../staffs.html", {
                        encoding: "utf-8",
                    });
        });
        it("[test 1] should pass with 200", async () => {
            // fetch のモックへ渡すデータを決める
            const dataElement = document.getElementById("id_data");
            const data = {
                status_code: 200, 
            }
            dataElement.value = JSON.stringify(data);
    
            //  fetchのモックの実装定義
            const windowFetch = require("../__mocks__/fetchTargetMonth");
            window.fetch = jest.fn().mockImplementation(windowFetch);
    
            const innerCallback = innerCallbackFunction;
    
            // create an Event
            let changeMonthBtn = document.getElementById('id_change_month_btn');
            const event = document.createEvent("Event");
            event.initEvent("click", true, false);    
            changeMonthBtn.dispatchEvent(event);
    
            expect.assertions(2);
            // イベントリスナーの登録＆実行
            await expect(innerCallback(event)).resolves.toBeUndefined();
            expect(fetch).toBeCalledTimes(1);
        });
        it("[test 2] fail inside the callback func. because no status code", async () => {
            // fetch のモックへ渡すデータを決める
            const dataElement = document.getElementById("id_data");
            const data = "";  // data が無いとfetchモックでエラーになる。
            dataElement.value = JSON.stringify(data);
    
            //  fetchのモックの実装定義
            const windowFetch = require("../__mocks__/fetchTargetMonth");
            window.fetch = jest.fn().mockImplementation(windowFetch);
    
            const innerCallback = innerCallbackFunction;
    
            // create an Event
            const changeMonthBtn = document.getElementById('id_change_month_btn');
            const event = document.createEvent("Event");
            event.initEvent("click", true, false);    
            changeMonthBtn.dispatchEvent(event);
    
            expect.assertions(2);
            // innerCallback 内では例外を投げるが、ここに戻るのは Promise
            await expect(innerCallback(event)).resolves.toBeUndefined();
            expect(fetch).toBeCalledTimes(1);
        });
    
        it("[test 3] throw error with 400 inside the callback cought in the outer catch", async () => {
            // fetch のモックへ渡すデータを決める
            const dataElement = document.getElementById("id_data");
            const data = {
                status_code: 400, 
            }
            dataElement.value = JSON.stringify(data);
    
            //  fetchのモックの実装定義
            const windowFetch = require("../__mocks__/fetchTargetMonth");
            window.fetch = jest.fn().mockImplementation(windowFetch);
    
            const innerCallback = innerCallbackFunction;
    
            // create an Event
            const changeMonthBtn = document.getElementById('id_change_month_btn');
            const event = document.createEvent("Event");
            event.initEvent("click", true, false);    
            changeMonthBtn.dispatchEvent(event);
    
            expect.assertions(2);
            // innerCallback 内では例外を投げるが、ここに戻るのは Promise
            await expect(innerCallback(event)).resolves.toBeUndefined();
            expect(fetch).toBeCalledTimes(1);
        });
    });
    
[/code]

fetchのモックファイル（js\ __mocks__\fetchTargetMonth.js）

[code]

        // DOMの一部を借りてデータの受け渡しをする
    const dataElement = document.getElementById("id_data");
    const response = JSON.parse(dataElement.value);
    
    const windowFetch = () => {
        return new Promise((resolve) => {
            if ( !response ) {
                throw Error('テストコードに response データを指定してください！');
            }
            if (response.status_code == 200) {
                resolve({
                    ok: true, 
                    status: response.status_code, 
                    json:  () => ((response))
                });
            } else {
                resolve({
                    ok: false,      // resolveするが、status.ok は false
                    status: response.status_code, 
                    json:  () => ((response))
                });
            }
        });
    };
    module.exports = windowFetch;
    
[/code]

staffs.html（関連する要素以外省略）

[code]

        <input type="number" name="year" value="2021" min="2000" style="width:70px;" class="form-control form-control-sm" required="" disabled="" id="id_year"> 年 <input type="number" name="month" value="7" style="width:50px;" class="form-control form-control-sm" required="" disabled="" id="id_month"> 月
    <input id="id_change_month_btn" class="btn btn-primary btn-sm form-control-sm" type="hidden" value="変更">
    ・・・略・・・
    <input type="hidden" id="id_data" value="">
    
[/code]

以上です。足りない情報などありましたら、コメントください。よろしくお願いします。


CC BY-SA 4.0
2021-07-09T03:53:01.363
78065
2021-07-13T01:00:27.880
2021-07-09T05:27:03.647
34891
34891
question
javascript テスト 非同期 jestjs
JavaScriptのテストですべての分岐をPASSしているのにカバレッジが100%にならない


いろいろ試しているうちに解決方法を発見しました。  
下記コードのようにbeforeEachの冒頭に「jest.resetModules();」を一行挿入するだけで、カバレッジが100％になりました。  
詳細なしくみまでは分かりませんが、連続でテストする際に、モジュールのキャッシュが使われて、前と同じ変数の値が使われていたようです。

[code]

    beforeEach(() => {
    
        jest.resetModules();
    
        // レンダリングされたstaffs.htmlを読み込む
        const fs = require("fs");
        document.body.innerHTML 
            = fs.readFileSync(__dirname + "/../staffs.html", {
                    encoding: "utf-8",
                });
    });
    
[/code]

お騒がせしました。

※追記：他のテストをやっていて、jest.resetModules();だけでは100％にならない場合もありました。公式APIドキュメントを見て、下記のコマンドで一度キャッシュを削除してから、もう一度「npm
test」を実行するとカバレッジが100%になりました。

[code]

    > npm test -- --clearCache
    
[/code]


CC BY-SA 4.0
2021-07-11T01:49:56.053
78101
2021-07-13T01:00:27.880
2021-07-13T01:00:27.880
34891
34891
78065
answer