Vue.js 测试框架 Jest + vue-test-utils 的介绍及使用

对于 Vue.js 开发者而言,测试是开发过程中必不可少的一环。而不同的测试框架有着不同的适用场景和运用方法。本文将介绍 Jest 和 vue-test-utils 两个测试框架的基本使用和相关实践,为 Vue.js 的测试提供指导意义。

Jest 简介

Jest 是一个基于 Jasmine 的测试框架,广泛用于 JavaScript 应用的单元、集成和端到端测试。相比其他测试框架,Jest 比较易于上手和学习,主要由以下几个特点贡献:

  • 快照测试(Snapshot Testing):Jest 基于现有功能自动创建组件的快照,当代码库出现变化时,可以方便快速地确定是否需要进行进一步处理以解决冲突。
  • 代码覆盖率(Code Coverage):Jest 可以帮助测试者确定测试库是否已经涵盖了代码库的大部分逻辑。尤其在大型代码库工程中,它有助于缩小测试范围和减少重复测试。
  • 优秀的 mock(Mock)支持:Jest 内置了 mocking 的支持,使测试者可以轻松地对依赖进行 mocking 和跟踪。
  • 无需配置(Zero Configuration):Jest 可以默认找到测试代码目录并开始运行测试。这使得 Jest 的使用跟踪和扩展非常简单。

使用 Jest 进行 Vue.js 测试

Jest 可以配合 Vue.js 组件及插件一起使用,并且 vue-test-utils 库进一步增强了 Jest 的组件测试相关功能。

安装及配置 Jest 和 vue-test-utils

首先,我们需要安装 Jest 和 vue-test-utils:

然后,在 package.json 中添加 Jest 的配置,例如:

{
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "jsx",
      "json",
      "vue"
    ],
    "transform": {
      "^.+\\.vue$": "vue-jest",
      ".+\\.(css|styl|less|sass|scss)$": "jest-transform-css",
      "^[^\\.]+(?<!\\.vue)$": "babel-jest"
    },
    "moduleNameMapper": {
      "^@/(.*)$": "<rootDir>/src/$1"
    }
  }
}

代码中的配置项分别代表:

  • moduleFileExtensions:定义代码库中支持的文件拓展名。
  • transform:为各种拓展名的文件定义转换器。
  • moduleNameMapper:定义 webpack alias 的映射。

测试 Vue 组件

Vue.js 组件是应用程序的核心,也是深入测试的重点。Vue.js 可以通过多种方式进行组件渲染,例如通过文件系统加载,本地加载等。以下是一些测试示例:

测试有数据的组件

<!-- components/Counter.vue -->
<template>
  <div>
    <span>{{ count }}</span>
    <button @click="increment">+</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count++;
    }
  }
};
</script>
// components/__tests__/Counter.test.js
import { mount } from "@vue/test-utils";
import Counter from "../Counter.vue";

describe("Counter", () => {
  test("increment button increments count", async () => {
    const wrapper = mount(Counter);

    expect(wrapper.text()).toContain("0");

    await wrapper.find("button").trigger("click");

    expect(wrapper.text()).toContain("1");
  });
});

测试 props 的组件

<!-- components/PropsExample.vue -->
<template>
  <div>
    <span>{{ message }}</span>
  </div>
</template>

<script>
export default {
  props: {
    message: String
  }
};
</script>
// components/__tests__/PropsExample.test.js
import { mount } from "@vue/test-utils";
import PropsExample from "../PropsExample.vue";

describe("PropsExample", () => {
  test("accepts a message prop", () => {
    const wrapper = mount(PropsExample, {
      propsData: {
        message: "hello world"
      }
    });

    expect(wrapper.text()).toContain("hello world");
  });
});

测试使用 Vuex 的组件

<!-- components/CounterWithVuex.vue -->
<template>
  <div>
    <span>{{ count }}</span>
    <button @click="increment">+</button>
  </div>
</template>

<script>
import { mapGetters, mapMutations } from "vuex";

export default {
  computed: {
    ...mapGetters(["count"])
  },
  methods: {
    ...mapMutations(["increment"])
  }
};
</script>
// components/__tests__/CounterWithVuex.test.js
import { mount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import CounterWithVuex from "../CounterWithVuex.vue";

const localVue = createLocalVue();

localVue.use(Vuex);

describe("CounterWithVuex", () => {
  let actions;
  let store;

  beforeEach(() => {
    actions = {
      increment: jest.fn()
    };
    store = new Vuex.Store({
      state: {
        count: 0
      },
      getters: {
        count: state => state.count
      },
      actions
    });
  });

  test("increment button calls increment action", async () => {
    const wrapper = mount(CounterWithVuex, { store, localVue });

    expect(wrapper.text()).toContain("0");

    await wrapper.find("button").trigger("click");

    expect(actions.increment).toHaveBeenCalled();
  });
});

vue-test-utils 简介

vue-test-utils 是适用于 Vue.js 的一个官方测试库,可以指导开发者进行测试以及提供用于操作 Vue.js 组件的工具。vue-test-utils 提供了交互式 DOM 操作 API 和一长串的辅助函数,可以开箱即用,并与大部分测试框架互通。

使用 vue-test-utils 进行 Vue.js 测试

vue-test-utils 提供多种 API 可以对组件的 DOM 元素、props、模拟事件进行覆盖。以下是一些测试示例:

测试有数据的组件

<!-- components/Counter.vue -->
<template>
  <div>
    <span>{{ count }}</span>
    <button @click="increment">+</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count++;
    }
  }
};
</script>
// components/__tests__/Counter.test.js
import { shallowMount } from "@vue/test-utils";
import Counter from "../Counter.vue";

describe("Counter", () => {
  test("increment button increments count", async () => {
    const wrapper = shallowMount(Counter);

    expect(wrapper.text()).toContain("0");

    await wrapper.find("button").trigger("click");

    expect(wrapper.text()).toContain("1");
  });
});

测试 props 的组件

<!-- components/PropsExample.vue -->
<template>
  <div>
    <span>{{ message }}</span>
  </div>
</template>

<script>
export default {
  props: {
    message: String
  }
};
</script>
// components/__tests__/PropsExample.test.js
import { shallowMount } from "@vue/test-utils";
import PropsExample from "../PropsExample.vue";

describe("PropsExample", () => {
  test("accepts a message prop", () => {
    const wrapper = shallowMount(PropsExample, {
      propsData: {
        message: "hello world"
      }
    });

    expect(wrapper.text()).toContain("hello world");
  });
});

测试使用 Vuex 的组件

<!-- components/CounterWithVuex.vue -->
<template>
  <div>
    <span>{{ count }}</span>
    <button @click="increment">+</button>
  </div>
</template>

<script>
import { mapGetters, mapMutations } from "vuex";

export default {
  computed: {
    ...mapGetters(["count"])
  },
  methods: {
    ...mapMutations(["increment"])
  }
};
</script>
// components/__tests__/CounterWithVuex.test.js
import { shallowMount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import CounterWithVuex from "../CounterWithVuex.vue";

const localVue = createLocalVue();

localVue.use(Vuex);

describe("CounterWithVuex", () => {
  let actions;
  let store;

  beforeEach(() => {
    actions = {
      increment: jest.fn()
    };
    store = new Vuex.Store({
      state: {
        count: 0
      },
      getters: {
        count: state => state.count
      },
      actions
    });
  });

  test("increment button calls increment action", async () => {
    const wrapper = shallowMount(CounterWithVuex, { store, localVue });

    expect(wrapper.text()).toContain("0");

    await wrapper.find("button").trigger("click");

    expect(actions.increment).toHaveBeenCalled();
  });
});

总结

Vue.js 的测试既需要对组件进行单元测试又需要进行指定数据以及组件相互作用的集成测试。Jest + vue-test-utils 可以帮助开发者轻松地管理和执行这些测试。本文通过介绍 Jest 和 vue-test-utils 的基本使用和实际操作,旨在为 Vue.js 开发者提供指导和拓展测试技能。大家如果希望进一步学习可以进一步拓展 Jest 以及基础单元测试相关技能,或者使用 Vue 官方提供的 Cypress (集成测试)框架。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65a0fa49add4f0e0ff924b0d


纠错反馈