组件测试内容

组件测试分为四个步骤:测试组件渲染,触发事件,界面更新。

文档地址:https://vue-test-utils.vuejs.org/api/
1、渲染组件

  • 通过mount 和 shallowMount方法获取整个组件
  • 传递的属性是否在组件正确显示出来。
    • 元素是否成功的显示
      • 查找元素的不同写法
      • get, getAll
      • find, findAll
      • findComponent 和 getComponent

2、触发事件

  • trigger 方法触发事件方法之后,界面是否更新。
    • 特别注意DOM更新是否异步的过程,要使用 async await。
  • 用setValue 方法更新表单之后,
    • 界面是否正常显示值。
    • 验证事件是否发送wrapper.emitted()

3、测试异步请求

  • jest.mock('axios')mock 了axios模块。
  • 分别mockAxios.get.mockResolvedValueOnce({ data: { username: 'viking'}})

mockAxios.get.mockRejectedValueOnce('error')模拟axios的正确返回和错误返回。
4、注意事项
我们需要一种钩子函数可以让我们做好测试前的准备和测试后的收尾

  • 一次性完成测试准备
    • beforeAll
    • afterAll
  • 每个测试测试准备
    • beforeEach
    • afterEach
  • 建议
    • 如果一个测试失败,需要注意的是
    • 他是否是唯一在运行的测试,使用it.only进行隔离
    • 使用beforeEach或者afterEach清空一些共享状态
  • 小tip

    • 使用 only 只运行一个用例
    • 使用 skip 跳过一个用例

      渲染组件

      mount 和shallowMount的区别

  • mount一股脑全都渲染

  • shallowMount只渲染组件本身,外来的子组件都不渲染
  • shallowMount更快,更适合单元测试,单元测试最重要的思想是隔离。

    find和get的区别

  • 找不到的时候,find返回null, case不会出错,

  • get throw错误, case报错
  • 通用规则总是使用get,除了你想判断一些元素不存在的时候,这种情况下使用find

find

  1. import { shallowMount } from "@vue/test-utils";
  2. import HelloWorld from "@/components/HelloWorld.vue";
  3. describe("HelloWorld.vue", () => {
  4. it("renders props.msg when passed", () => {
  5. const msg = "new message";
  6. const wrapper = shallowMount(HelloWorld, {
  7. props: { msg },
  8. });
  9. console.log("测试HelloWorld组件");
  10. console.log(wrapper.find("h2"));
  11. });
  12. });

输出结果:
HelloWorld.vue 组件里面不存在h1标签,此时find(“h2”)返回null

  1. ts-jest[versions] (WARN) Version 4.1.6 of typescript installed has not been tested with ts-jest. If you're experiencing issues, consider using a supported version (>=2.7.0 <4.0.0). Please do not report issues in ts-jest if you are using unsupported versions.
  2. PASS tests/unit/example.spec.ts
  3. HelloWorld.vue
  4. renders props.msg when passed (27ms)
  5. console.log tests/unit/example.spec.ts:10
  6. 测试HelloWorld组件
  7. console.log tests/unit/example.spec.ts:11
  8. [Object: null prototype] {}
  9. Test Suites: 1 passed, 1 total
  10. Tests: 1 passed, 1 total
  11. Snapshots: 0 total
  12. Time: 4.985s, estimated 5s
  13. Ran all test suites related to changed files.
  14. Watch Usage: Press w to show more.

get

  1. import { shallowMount } from "@vue/test-utils";
  2. import HelloWorld from "@/components/HelloWorld.vue";
  3. describe("HelloWorld.vue", () => {
  4. it("renders props.msg when passed", () => {
  5. const msg = "new message";
  6. const wrapper = shallowMount(HelloWorld, {
  7. props: { msg },
  8. });
  9. console.log("测试HelloWorld组件");
  10. console.log(wrapper.get("h2"));
  11. });
  12. });

输出结果: 抛出错误

  1. ts-jest[versions] (WARN) Version 4.1.6 of typescript installed has not been tested with ts-jest. If you're experiencing issues, consider using a supported version (>=2.7.0 <4.0.0). Please do not report issues in ts-jest if you are using unsupported versions.
  2. FAIL tests/unit/example.spec.ts
  3. HelloWorld.vue
  4. × renders props.msg when passed (33ms)
  5. HelloWorld.vue renders props.msg when passed
  6. Unable to get h2 within: <h1>new message</h1><button>1</button>
  7. <hello-stub msg="1234"></hello-stub>
  8. 9 | });
  9. 10 | console.log("测试HelloWorld组件");
  10. > 11 | console.log(wrapper.get("h2"));
  11. | ^
  12. 12 | });
  13. 13 | });
  14. 14 |
  15. at VueWrapper.Object.<anonymous>.VueWrapper.get (node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js:7509:15)
  16. at Object.<anonymous> (tests/unit/example.spec.ts:11:25)
  17. console.log tests/unit/example.spec.ts:10
  18. 测试HelloWorld组件
  19. Test Suites: 1 failed, 1 total
  20. Tests: 1 failed, 1 total
  21. Snapshots: 0 total
  22. Time: 5.027s
  23. Ran all test suites related to changed files.
  24. Watch Usage: Press w to show more

findComponent 和 getComponent

寻找组件里面是否包含另一个组件,区别和上面一样。
不必测试子组件里面的内容,只要判断是否渲染了子组件,是否传递了正确的属性就可以了,这就是单元测试的意义,独立,互不影响的模块。

触发事件

trigger 方法,异步更新测试

异步事件:点击,Promise等等,需要通过异步更新来进行测试。
也就是在 触发元素事件的时候,await一下。

  1. import { shallowMount } from "@vue/test-utils";
  2. import HelloWorld from "@/components/HelloWorld.vue";
  3. import Hello from "@/components/Hello.vue";
  4. describe("HelloWorld.vue", () => {
  5. // 点击 button 时, count 是否更新
  6. it("should update the count when clicking the button", async () => {
  7. const msg = "new message";
  8. const wrapper = shallowMount(HelloWorld, {
  9. props: { msg },
  10. });
  11. // 获取 button 触发点击事件 。注意这里一定要加await
  12. await wrapper.get("button").trigger("click"); // 异步更新
  13. // 获取 button 的文本,是否等于2
  14. expect(wrapper.get("button").text()).toBe("2");
  15. });
  16. });

setValue 表单输入 更新测试

  1. import { shallowMount } from "@vue/test-utils";
  2. import HelloWorld from "@/components/HelloWorld.vue";
  3. import Hello from "@/components/Hello.vue";
  4. describe("HelloWorld.vue", () => {
  5. // 输入内容 点击button 查看列表是否更新
  6. it("should add todo when fill the input and click the add button", async () => {
  7. const msg = "new message";
  8. const todoContent = "buy milk";
  9. const wrapper = shallowMount(HelloWorld, {
  10. props: { msg },
  11. });
  12. await wrapper.get("input").setValue(todoContent); // 表单输入内容
  13. expect(wrapper.get("input").element.value).toBe(todoContent); // 查看input的值是否等于输入的内容
  14. await wrapper.get(".addTodo").trigger("click"); // 点击button,执行了添加事件
  15. expect(wrapper.findAll("li")).toHaveLength(1); // 查看所有li的数组长度是否为1
  16. expect(wrapper.get("li").text()).toBe(todoContent); // 获取第一个li,查看是否等于输入的内容
  17. });
  18. });

emitted 验证事件是否发送

  1. import { mount } from '@vue/test-utils'
  2. test('emit demo', async () => {
  3. const wrapper = mount(Component)
  4. wrapper.vm.$emit('foo')
  5. wrapper.vm.$emit('foo', 123)
  6. await wrapper.vm.$nextTick() // Wait until $emits have been handled
  7. /*
  8. wrapper.emitted() returns the following object:
  9. {
  10. foo: [[], [123]]
  11. }
  12. */
  13. // assert event has been emitted
  14. expect(wrapper.emitted().foo).toBeTruthy()
  15. // assert event count
  16. expect(wrapper.emitted().foo.length).toBe(2)
  17. // assert event payload
  18. expect(wrapper.emitted().foo[1]).toEqual([123])
  19. })

测试异步请求

  • 不要依赖第三方库,模拟第三方库的实现我们应该已经掌握
  • 使用flushPromises将所有Promise pending状态都改为完成
  • 小知识点

    • 将mock对象断言为特定类型,使用jest.Mocked<>。这样就有代码提示了。
    • 使用only跑一个单独的case
      1. import axios from 'axios';
      2. jest.mock('axios')
      3. // 使用断言,让模拟的axios使用api后,可以正常调用mock的api
      4. const mockAxios = axios as jest.Mocked<typeof axios>
  • 测试异步成功的结果 ```javascript import { flushPromises, shallowMount } from “@vue/test-utils”; import HelloWorld from “@/components/HelloWorld.vue”; import Hello from “@/components/Hello.vue”; import axios from “axios”; jest.mock(“axios”); const mockAxios = axios as jest.Mocked; // 使用断言,让模拟的axios使用api后,可以正常调用mock的api

describe(“HelloWorld.vue”, () => { // 使用only跑一个单独的case // 点击按钮 测试 user 数据的加载 成功 it.only(“should load user message when click the load button”, async () => { const msg = “new message”; const wrapper = shallowMount(HelloWorld, { props: { msg }, }); // 模拟接口,获取一次成功态,返回数据 mockAxios.get.mockResolvedValueOnce({ data: { userName: “viking” }, }); await wrapper.get(“.loadUser”).trigger(“click”); expect(mockAxios.get).toHaveBeenCalled(); // 点击按钮之后,是否被调用 expect(wrapper.find(“.loading”).exists()).toBeTruthy(); // 判断 .loading 元素是否存在(显示loading) await flushPromises(); // 将所有Promise pending状态都改为完成 // 界面更新完毕 expect(wrapper.find(“.loading”).exists()).toBeFalsy(); // 判断 .loading 元素是否存在(隐藏loading) expect(wrapper.get(“.userName”).text()).toBe(“viking”); }); });

  1. 测试异步失败的结果
  2. ```javascript
  3. import { flushPromises, shallowMount } from "@vue/test-utils";
  4. import HelloWorld from "@/components/HelloWorld.vue";
  5. import Hello from "@/components/Hello.vue";
  6. import axios from "axios";
  7. jest.mock("axios");
  8. const mockAxios = axios as jest.Mocked<typeof axios>; // 使用断言,让模拟的axios使用api后,可以正常调用mock的api
  9. describe("HelloWorld.vue", () => {
  10. // 点击按钮 测试 user 数据的加载 失败
  11. it.only("should load error when return promise reject", async () => {
  12. const msg = "new message";
  13. const wrapper = shallowMount(HelloWorld, {
  14. props: { msg },
  15. });
  16. // 模拟接口,获取一次失败态,返回数据
  17. mockAxios.get.mockRejectedValueOnce('error');
  18. await wrapper.get(".loadUser").trigger("click");
  19. await flushPromises(); // 将所有Promise pending状态都改为完成
  20. // 界面更新完毕
  21. expect(wrapper.find(".loading").exists()).toBeFalsy(); // 判断 .loading 元素是否存在(隐藏loading)
  22. expect(wrapper.find(".error").exists()).toBeTruthy();
  23. });
  24. });