前端抢饭碗系列之Vue项目中如何做单元测试建议收藏

Posted 小马哥说测试

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端抢饭碗系列之Vue项目中如何做单元测试建议收藏相关的知识,希望对你有一定的参考价值。

 异步代码

  我们项目中经常也会涉及到异步代码,比如setTimeout、接口请求等都会涉及到异步,那么这些异步代码怎么来进行测试呢?假设我们有一个异步获取数据的函数fetchData:

  export function fetchData(cb) {

    setTimeout(() => {

      cb("res data");

    }, 2000);

  }

  在2秒后通过回调函数返回了一个字符串,我们可以在测试用例的函数中使用一个done的参数,Jest会等done回调后再完成测试:

  test("callback", (done) => {

    function cb(data) {

      try {

        expect(data).toBe("res data");

        done();

      } catch (error) {

        done();

      }

    }

    fetchData(cb);

  });

  我们将一个回调函数传入fetchData,在回调函数中对返回的数据进行断言,在断言结束后需要调用done;如果最后没有调用done,那么Jest不知道什么时候结束,就会报错;在我们日常代码中,都会通过promise来获取数据,将我们的fetchData进行一下改写:

  export function fetchData() {

    return new Promise((resolve, reject) => {

      setTimeout(() => {

        resolve("promise data");

      }, 2000);

    });

  }

  Jest支持在测试用例中直接返回一个promise,我们可以在then中进行断言:

  test("promise callback", () => {

    return fetchData().then((res) => {

      expect(res).toBe("promise data");

    });

  });

  除了直接将fetchData返回,我们也可以在断言中使用.resolves/.rejects 匹配符,Jest也会等待promise结束:

  test("promise callback", () => {

    return expect(fetchData()).resolves.toBe("promise data");

  });

  除此之外,Jest还支持async/await,不过我们需要在test的匿名函数加上async修饰符表示:

  test("async/await callback", async () => {

    const data = await fetchData();

    expect(data).toBe("promise data");

  });

  全局挂载与卸载

  全局挂载和卸载有点类似Vue-Router的全局守卫,在每个导航触发前和触发后做一些操作;在Jest中也有,比如我们需要在每个测试用例前初始化一些数据,或者在每个测试用例之后清除数据,就可以使用beforeEach和afterEach:

  let cityList = []

  beforeEach(() => {

    initializeCityDatabase();

  });

  afterEach(() => {

    clearCityDatabase();

  });

  test("city data has suzhou", () =>  {

    expect(cityList).toContain("suzhou")

  })

  test("city data has shanghai", () =>  {

    expect(cityList).toContain("suzhou")

  })

  这样,每个测试用例进行测试前都会调用init,每次结束后都会调用clear;我们有可能会在某些test中更改cityList的数据,但是在beforeEach进行初始化的操作后,每个测试用例获取的cityList数据就保证都是相同的;和上面一节异步代码一样,在beforeEach和afterEach我们也可以使用异步代码来进行初始化:

  let cityList = []

  beforeEach(() => {

    return initializeCityDatabase().then((res)=>{

      cityList = res.data

    });

  });

  //或者使用async/await

  beforeEach(async () => {

    cityList = await initializeCityDatabase();

  });

  和beforeEach和afterEach相对应的就是beforeAll和afterAll,区别就是beforeAll和afterAll只会执行一次;beforeEach和afterEach默认会应用到每个test,但是我们可能希望只针对某些test,我们可以通过describe将这些test放到一起,这样就只应用到describe块中的test:

  beforeEach(() => {

    // 应用到所有的test

  });

  describe("put test together", () => {

    beforeEach(() => {

      // 只应用当前describe块中的test

    });

    test("test1", ()=> {})

    test("test2", ()=> {})

  });

  模拟函数

  在项目中,一个模块的函数内常常会去调用另外一个模块的函数。在单元测试中,我们可能并不需要关心内部调用的函数的执行过程和结果,只想知道被调用模块的函数是否被正确调用,甚至会指定该函数的返回值,因此模拟函数十分有必要。

  如果我们正在测试一个函数forEach,它的参数包括了一个回调函数,作用在数组上的每个元素:

  export function forEach(items, callback) {

    for (let index = 0; index < items.length; index++) {

      callback(items[index]);

    }

  }

  为了测试这个forEach,我们需要构建一个模拟函数,来检查模拟函数是否按照预期被调用了:

  test("mock callback", () => {

    const mockCallback = jest.fn((x) => 42 + x);

    forEach([0, 1, 2], mockCallback);

    expect(mockCallback.mock.calls.length).toBe(3);

    expect(mockCallback.mock.calls[0][0]).toBe(0);

    expect(mockCallback.mock.calls[1][0]).toBe(1);

    expect(mockCallback.mock.calls[2][0]).toBe(1);

    expect(mockCallback.mock.results[0].value).toBe(42);

  });

  我们发现在mockCallback有一个特殊的.mock属性,它保存了模拟函数被调用的信息;我们打印出来看下:

  它有四个属性:

  · calls:调用参数

  · instances:this指向

  · invocationCallOrder:函数调用顺序

  · results:调用结果

  在上面属性中有一个instances属性,表示了函数的this指向,我们还可以通过bind函数来更改我们模拟函数的this:

  test("mock callback", () => {

      const mockCallback = jest.fn((x) => 42 + x);

      const obj = { a: 1 };

      const bindMockCallback = mockCallback.bind(obj);

      forEach([0, 1, 2], bindMockCallback);

      expect(mockCallback.mock.instances[0]).toEqual(obj);

      expect(mockCallback.mock.instances[1]).toEqual(obj);

      expect(mockCallback.mock.instances[2]).toEqual(obj);

  });

  通过bind更改函数的this之后,我们可以用instances来进行检测;模拟函数可以在运行时将返回值进行注入:

  const myMock = jest.fn();

  // undefined

  console.log(myMock());

  myMock

      .mockReturnValueOnce(10)

      .mockReturnValueOnce("x")

      .mockReturnValue(true);

  //10 x true true

  console.log(myMock(), myMock(), myMock(), myMock());

  myMock.mockReturnValueOnce(null);

  // null true true

  console.log(myMock(), myMock(), myMock());

  我们第一次执行myMock,由于没有注入任何返回值,然后通过mockReturnValueOnce和mockReturnValue进行返回值注入,Once只会注入一次;模拟函数在连续性函数传递返回值时使用注入非常的有用:

  const filterFn = jest.fn();

  filterFn.mockReturnValueOnce(true).mockReturnValueOnce(false);

  const result = [2, 3].filter((num) => filterFn(num));

  expect(result).toEqual([2]);

  我们还可以对模拟函数的调用情况进行断言:

  const mockFunc = jest.fn();

  // 断言函数还没有被调用

  expect(mockFunc).not.toHaveBeenCalled();

  mockFunc(1, 2);

  mockFunc(2, 3);

  // 断言函数至少调用一次

  expect(mockFunc).toHaveBeenCalled();

  // 断言函数调用参数

  expect(mockFunc).toHaveBeenCalledWith(1, 2);

  expect(mockFunc).toHaveBeenCalledWith(2, 3);

  // 断言函数最后一次的调用参数

  expect(mockFunc).toHaveBeenLastCalledWith(2, 3);

  除了能对函数进行模拟,Jest还支持拦截axios返回数据,假如我们有一个获取用户的接口:

  // /src/api/users

  const axios = require("axios");

  function fetchUserData() {

    return axios

      .get("/user.json")

      .then((resp) => resp.data);

  }

  module.exports = {

    fetchUserData,

  };

  现在我们想要测试fetchUserData函数获取数据但是并不实际请求接口,我们可以使用jest.mock来模拟axios模块:

  const users = require("../api/users");

  const axios = require("axios");

  jest.mock("axios");

  test("should fetch users", () => {

    const userData = {

      name: "aaa",

      age: 10,

    };

    const resp = { data: userData };

    axios.get.mockResolvedValue(resp);

    return users.fetchUserData().then((res) => {

      expect(res).toEqual(userData);

    });

  });

  一旦我们对模块进行了模拟,我们可以用get函数提供一个mockResolvedValue方法,以返回我们需要测试的数据;通过模拟后,实际上axios并没有去真正发送请求去获取/user.json的数据。

最后为方便大家学习测试,特意给大家准备了一份13G的超实用干货学习资源,涉及的内容非常全面。


包括,软件学习路线图,50多天的上课视频、16个突击实战项目,80余个软件测试用软件,37份测试文档,70个软件测试相关问题,40篇测试经验级文章,上千份测试真题分享,还有2021软件测试面试宝典,还有软件测试求职的各类精选简历,希望对大家有所帮助……

关注我公众号:【程序员小濠】即可获取这份资料了!

如果你不想再体验一次自学时找不到资料,没人解答问题,坚持几天便放弃的感受的话,可以加入我们的群:175317069 大家一起讨论交流,里面也有各种软件测试资料和技术交流。
 

好文推荐

5年经验之谈:月薪3000到30000,测试工程师的变“行”记!

测试工程师,自动化测试工程师,测试开发工程师,这三个岗位分别需要掌握哪些能力和技术栈?

不要让毒鸡汤毁了你,35岁的测试员没有那么可怕,保持专注更重要

 

以上是关于前端抢饭碗系列之Vue项目中如何做单元测试建议收藏的主要内容,如果未能解决你的问题,请参考以下文章

学了vue后,再也不怕美工抢饭碗了,前端小姐姐也变的热情了

十分钟了解前端单元测试技术方案(附源码,建议收藏!)

2W字学习 Vue 应用测试,让你的项目更加健壮和稳定

自动驾驶抢饭碗? 老司机们说出了 所有卡友们担心的问题!

服务器部署—若依vue如何部署到nginx里面?nginx刷新页面404怎么办?完美解决建议收藏

一文搞定Spring Boot + Vue 项目在Linux Mysql环境的部署(强烈建议收藏)