如何給 Vue 的單元測試填充模塊檔?

TL;DR

Jest 有個 jest.mock 可以給 Vue 的單元測試,填充各種模塊檔。方法有兩種:

  1. 在模塊檔的目錄建立 __mocks__ 給 Jest 用。然後 jest.mock("模塊檔位置")
  2. 在測試檔給 jest.mock 的函式帶參數 jest.mock("模塊檔位置", "模塊檔結構模擬")

問題

今天寫測試,寫了像是這樣的東東:

<template>
    <main>
        <p v-show="show">{{ text }}</p>
    </main>
</template>

<script>
import { SomeAJAXAction } from "@/api/foobar.js";

export default {
    name: "FooBar2000",
    props: {
        text: {
            type: String,
            default: "An example",
        }
    },
    data: () => ({
        show: true,
    }),
    async created() {
        const res = await SomeAJAXAction( /* Some AJAX params */ );
        this.show = res.data.data;
    },
}
</script>

想說寫測試嘛,好像是 Jest 嗎?應該是這樣寫吧:

// test/unit/FooBar2000.soec.js
import { shallowMount } from "@vue/test-utils";
import FooBar2000 from "@/components/FooBar2000.vue"; // 慣例上 @/ 等於 src/

describe("FooBar2000", () => {
    it("should render given text", () => {
        wrapper = shallowMount(Home);
        expect(wrapper.vm.text).toBe("An example");
    });
});

但出現一些麻煩的錯誤。

錯誤

關於錯誤的具體情況省略,簡單來說,就是 AJAX 函式 SomeAJAXAction 需要瀏覽器的 window 屬性。但測試檔案因為是跑 node.js 的,當然不可能有 window 屬性。

我有試過填充 window 屬性,但幾乎沒有用。那怎麼辦呢?

「何不直接填充 SomeAJAXAction 函式?」

但怎麼做呢?

解決

正好 Vue.js Developers 有人寫下了解法。參考文章的作法,我這麼寫:

// src/api/__mocks__/foobar.js
export default {
    SomeAJAXAction: () => ({ data: { data: true } })
};
// test/unit/FooBar2000.soec.js
import { shallowMount } from "@vue/test-utils";
import FooBar2000 from "@/components/FooBar2000.vue";

jest.mock("@/api/foobar.js");

describe("FooBar2000", () => {
    it("should render given text", () => {
        wrapper = shallowMount(Home);
        expect(wrapper.vm.text).toBe("An example");
    });
});

然後跑了測試,果然過了!Jest 真的會吃 __mocks__ 檔案下的東西!

其他辦法

那麼有其他辦法嗎?有。還真的有。Jest 的說明手冊寫在 Mocking Partials at Mock Functions

按照說明,我刪掉了 src/api/__mocks__/foobar.js 的檔案,把程式搬到測試:

// test/unit/FooBar2000.soec.js
import { shallowMount } from "@vue/test-utils";
import FooBar2000 from "@/components/FooBar2000.vue";

jest.mock("@/api/foobar.js", {
    SomeAJAXAction: () => ({ data: { data: true } })
});

describe("FooBar2000", () => {
    it("should render given text", () => {
        wrapper = shallowMount(Home);
        expect(wrapper.vm.text).toBe("An example");
    });
});

這樣也通喔!

你甚至能給 AJAX Library 這麼引:

<script>
import axios from "axios";
export default {
    async created() {
        const res = await axios({
            url: "/some/api",
            method: "GET",
            params: {
                foo: "bar",
            },
        });
    },
}
</script>
// test/unit/FooBar2000.soec.js
import { shallowMount } from "@vue/test-utils";
import FooBar2000 from "@/components/FooBar2000.vue";
import axios from "axios";

jest.mock("axios");

describe("FooBar2000", () => {
    it("should render given text", () => {
        axios.mockResolvedValue({ data: true });
    });
});

參考資料