Vue 单元测试:如何使用 props、vuex store、watchers、getter 等测试复杂组件

Posted

技术标签:

【中文标题】Vue 单元测试:如何使用 props、vuex store、watchers、getter 等测试复杂组件【英文标题】:Vue Unit Testing: How to test complex components with props, vuex store, watchers, getters etc 【发布时间】:2020-03-14 07:20:23 【问题描述】:

我有一个关于 vue 应用程序中的单元测试 (Jest) 的问题 - 我想这是一个架构问题,但也许具体的代码示例也可以帮助我。

我已经从我的应用中勾勒出一个或多或少复杂的 vue 组件设置。它们仍然被简化,但我想它们应该有助于描述这种情况。

(PDF Attachment for those preferring non-pixel data :)

我有一个 Wrapper,它包含三个子组件:

搜索(在地图上搜索的输入字段) 列表(商店列表,也表示为地图上的标记) 地图(谷歌地图本身)

我真的很想为此编写一些单元测试,但是设置这些测试似乎非常繁琐,所以我怀疑这是不是这样做的方法。

它从我加载谷歌地图 API 的方式开始:包装器异步加载它,设置 API 密钥等。一旦加载谷歌地图 API,它就会将其传递给子级。

当然,地图只有在google 出现并且我可以使用new this.google.maps.Map() 时才真正开始加载、创建和显示标记。

即使我想测试 Map 组件的某些方法,我也必须提供一个实际的 google 对象,否则mount() 中的整个初始化过程都会失败。它不能制作地图。

那么我必须在我的测试中加载实际的谷歌地图 API 吗?或者我可以以某种方式跳过安装地图,但仍然测试一些孤立的方法?

进一步你可以看到我的 Map 从 vuex 商店中观察一个 getter:每当我的 vuex 商店中 Search 设置的searchCoordinates 发生变化时,我想执行一些功能(例如,如果搜索未返回有效的搜索坐标,则将我的地图居中到新的搜索坐标或删除当前位置标记)。

我怎样才能以某种方式打破这些组件之间的相互联系,但仍然编写好的和有用的测试,这有助于我保证应用程序做它应该做的事情?

我知道我可以在我的测试中使用 vuex 存储 (Medium article about vuex testing)。 但我仍然觉得整个应用程序是如此相互关联,以至于进行实际的单元测试——一次测试一小块——是不可能的。 看来我不仅要单独安装 Map,还要安装其他组件和带有实际数据的 vuex 存储,才能真正编写防弹测试。

其他几个例子,我很纠结:

在我的mounted() 中,我使用this.$store.subscribeAction((action) => ... 订阅了一个操作 - 当然 $store 在我的测试中是未定义的。 Should I mock it? 正如我所说,我正在观察一个吸气剂:这会引发像 TypeError: Cannot use 'in' operator to search for 'searchCoordinates' in undefined 这样的错误。我是否也必须嘲笑所有的吸气剂?如果是这样:还有什么需要测试的?如果我模拟(~=离开)所有 vuex 的东西,在我看来,测试变得毫无用处......

但也许我在这里弄错了。我对单元测试也很陌生,所以非常欢迎任何提示。 非常感谢您提前。我希望有人有一些有价值的意见。 所有这些例子(即使在像Vue.js - up and running 这样的书中)总是被分解为非常简单易懂的例子,但另一方面并不能真正反映现实生活中的应用......

干杯

----- 编辑:

我得到了某人的needs more focus 投票。 所以我试着总结简单的小问题:

问题摘要

    有人对如何测试更大、更复杂的 vue 应用程序有一般意见吗? 有没有办法用谷歌地图测试组件(或严重依赖任何第三方代码的组件) 在这种情况下,模拟 getter、store 和 action 是个好主意吗? (我对此有疑问)。 为了测试来自 vuex 存储的监视属性,我读到 in this thread 应该模拟 mapState。 (在我的情况下可能是我的吸气剂)。在我的情况下嘲笑那些吸气剂有意义吗? 最好的做法是在测试的组件中尽可能多地简化事情(例如,我开始重写一些方法,但这些方法失败了(包括谷歌地图启动的方法)以使我的测试启动并运行:李>
const initMapMock = jest.fn();
const initMarkersMock = jest.fn();
...
const wrapper = shallowMount(Map, 
    methods: 
        initMap: initMapMock, // replace initMap(), which needs google map API
        initMarkers: initMarkersMock,
      ,
    ...

这有意义吗?

【问题讨论】:

【参考方案1】:

深入研究这个问题后,我意识到一件事: 如果有人遇到上述这样复杂的情况,那么单元测试可能不是解决此问题的正确工具!

在这种情况下,end to end testing 也称为 integration testing 会更适合这项工作。

因为我真正想要测试的是,所有不同的组件都以正确的方式做出反应,如果某些事情发生了变化/触发等等,我需要看到整个画面。

如果我回到我的示例,我想看看如果搜索输入记录了变化等情况下地图会发生变化。

我最终为此集成了一些测试,例如使用无头浏览器 googles puppeteer、打开网页、填写搜索查询然后搜索 DOM 以查找地图中的可靠更改。

不幸的是,由于预算限制,我没有时间真正详细地对此进行测试,但我知道应该如何处理。

我最初模拟商店等的想法需要大量工作,而这只不过是围绕单元测试构建一个类似于应用程序其余部分的大型模拟,这样做是完全愚蠢的。通过集成测试,我使用这个已经构建的应用程序来完成我的测试,但我的优势是能够同步测试所有内容!

我个人也认为集成测试比单元测试更强大,因为它们阐明了开发人员不容易掌握的应用程序部分,并且如果应用程序的一部分被更改并且其他无法按预期运行。

如果有人有更多的补充,我仍然很高兴听到关于此事的意见。

干杯

【讨论】:

以上是关于Vue 单元测试:如何使用 props、vuex store、watchers、getter 等测试复杂组件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 vue-test-utils 和 Jest 在 Nuxt 中对使用 vuex-module-decorators 语法定义的 Vuex 模块进行单元测试?

如何在 VueJS 中对 Vuex 动作进行单元测试

如何解决 vuex 和单元测试的问题?

vue-test-utils:如何在 mount() 生命周期钩子(使用 vuex)中测试逻辑?

如何在 Mocha 单元测试中访问命名空间的 Vuex getter

为啥 VueX 存储在多个单元测试中保留状态?