【问题标题】:How to Test a Global Event Bus With Vue Test Utils?如何使用 Vue Test Utils 测试全局事件总线?
【发布时间】:2018-07-07 06:22:13
【问题描述】:

我正在尝试学习如何测试通过全局事件总线发出的事件。这是我不知道该怎么做的地方的一些cmets的代码。

// EvtBus.js
import Vue from 'vue';
export const EvtBus = new Vue();
<!-- CouponCode.vue -->
<template>
    <div>
        <input
            class="coupon-code"
            type="text"
            v-model="code"
            @input="validate">
        <p v-if="valid">
            Coupon Redeemed: {{ message }}
        </p>
    </div>
</template>

<script>

import { EvtBus } from '../EvtBus.js';

export default {
    data () {
        return {
            code: '',
            valid: false,

            coupons: [
                {
                    code: '50OFF',
                    discount: 50,
                    message: '50% Off!'
                },
                {
                    code: 'FREE',
                    discount: 100,
                    message: 'Entirely Free!'
                }
            ]
        };
    },

    created () {
        EvtBus.$on('coupon-applied', () => {
            //console.info('had a coupon applied event on component');
        });
    },

    methods: {
        validate () {
            // Extract the coupon codes into an array and check if that array
            // includes the typed in coupon code.
            this.valid = this.coupons.map(coupon => coupon.code).includes(this.code);
            if (this.valid) {
                this.$emit('applied');
                // I NEVER see this on the coupon-code.spec.js
                EvtBus.$emit('coupon-applied');
            }
        }
    },

    computed: {
        message () {
            return this.coupons.find(coupon => coupon.code === this.code).message;
        }
    }
}
</script>
// tests/coupon-code.spec.js
import expect from 'expect';
import { mount } from '@vue/test-utils';
import CouponCode from '../src/components/CouponCode.vue';
import { EvtBus } from '../src/EvtBus.js';

describe('Reminders', () => {
    let wrp;

    beforeEach(() => {
        wrp = mount(CouponCode);
    });

    it('broadcasts the percentage discount when a valid coupon code is applied', () => {
        let code = wrp.find('input.coupon-code');
        code.element.value = '50OFF';
        code.trigger('input');

        console.log(wrp.emitted('applied'));

        //
        // I NEVER see this on the outpout.
        // How can I test it through a global event bus rather than
        // an event emitted from the component instance?
        //
        EvtBus.$on('coupon-applied', () => {
            console.log('coupon was applied through event bus');
        });

        // Passes, but not using EvtBus instance.
        expect(wrp.emitted('applied')).toBeTruthy;

    });
});

所以,我的疑问是如何测试全局事件总线是否正在发出和监听使用该事件总线的组件内部的事件。

那么,是否可以使用 Vue Test Utils 测试全局事件总线,或者我应该使用其他方法?

【问题讨论】:

  • 你真的用过这个吗?我也有类似的情况,它的渲染测试对我来说几乎没用。

标签: unit-testing vue.js vue-test-utils


【解决方案1】:

如果组件正在使用全局 EventBus,例如在给定组件之外导入并分配给 window.EventBus,那么可以使用全局 Vue 实例将 $on 或 $emit 事件重定向到包装器的 vm 实例。这样您就可以继续编写测试,就好像组件是通过this.$emit 而不是EventBus.$emit 发出的:

it('clicking "Settings" button emits "openSettings"', () => {
    global.EventBus = new Vue();
    global.EventBus.$on('openSettings', (data) => {
        wrapper.vm.$emit('openSettings', data);
    });

    // component emits `EventBus.$emit('openSettings')`

    expect(wrapper.emitted('openSettings')).toBeTruthy(); // pass
});

【讨论】:

  • 对于 ES6 样式的组件,将其附加到全局 / 窗口对象不是一个好习惯。这是使其可测试的唯一方法吗?
  • 我在提供给shallowMountoptions对象的moks属性内创建了一个EventBus属性,其值为new Vue()
  • @DiegoAlbertoZapataHäntsch,你能举个例子吗?
  • @izumeroot 你可以在这里看到我的配置测试的摘录codesandbox.io/s/proud-butterfly-943tn?file=/src/index.js
  • 谢谢@DiegoAlbertoZapataHäntsch,所以您可以使用expect($EventBus.$emit).toHaveBeenCalledWith('event-name') 之类的东西,而不是expect($EventBus.emitted('event-name')).toHaveLength(1)。您能否展示资产示例?
【解决方案2】:

嗯,

EvtBus.$on('coupon-applied', () => {
    console.log('coupon was applied through event bus');
});

您的规范文件中的这段代码不会被调用,因为安装的 wrp 组件没有使用您在上面的规范文件中导入的相同 EvtBus。

您需要测试一个名为 inject-loader 的 npm 包,以便您可以提供您自己的优惠券代码组件的 EvtBus 依赖项的实现(存根)。

有点像这样

const couponCodeInjector = require('!!vue-loader?inject!src/views/CouponCode');

const stubbedModules = {
   '../EvtBus.js': {
        $on : sandbox.spy((evtName, cb) => cb()); 
    }
};

const couponCode = couponCodeInjector(stubbedModules);

然后在您的单元测试中,您可以在 code.trigger('input'); 时断言 stubbedModules['../EvtBus.js'].$on 是否已被调用;

PS:我没有使用过 vue-test-utils。所以我不知道如何使用这个 npm 包进行存根。

但您需要做的主要事情是找到一种方法来存根 CouponCode 组件中的 EvtBus 依赖项,以便您可以对其应用间谍并检查该间谍是否已被调用。

【讨论】:

  • 我希望我可以不再依赖。也许vue-test-utils 中有一些东西可以帮助我解决这个问题。我还没有阅读整个文档。我不明白你$on : sandbox.spy(...) 的事情,我今天会做一些研究。
  • 我试过了,但还不能让你的方法奏效。如果我成功了,我会告诉你的。感谢您的帮助。
【解决方案3】:

单元测试应该专注于单独测试单个组件。在这种情况下,您想测试是否发出事件,因为这是 CouponCode.vue 的工作。请记住,单元测试应该专注于测试最小的代码单元,并且一次只测试一件事。在这种情况下,我们关心事件是否被发出—— EventBus.test.js 是我们测试事件发出时会发生什么的地方。

不知道toBeTruthy 是一个函数——你需要()expect(wrp.emitted('applied')).toBeTruthy 实际上没有通过,因为您需要 () - 目前,它实际上什么也没做 - 没有做出任何断言。

你的断言应该是这样的:

expect(wrp.emitted('applied')).toBeTruthy()

您可以更进一步,通过执行expect(wrp.emitted().applied.length).toBe(1) 之类的操作来确保它只发出一次。

然后您也可以单独测试InputBus。如果您可以发布该组件的代码,我们可以研究如何对其进行测试。

我最近开发了一个大型 Vue 应用程序,并为主要的 repo 和文档做出了很多贡献,所以我很乐意尽我所能提供帮助。

如果这有帮助或者您需要更多指导,请告诉我。如果可能,也发布 EventBus.vue。

【讨论】:

  • 感谢您发现丢失的().toBeTruthy。请注意,虽然我使用了wrp.emitted,但这只是为了说明目的,因为我的目标是学习如何测试全局事件总线。 EvtBus.js 包含在开场白中。
  • EvtBus 是可以测试的,但是应该和这个 CouponCode.vue 分开测试。我认为您不应该在 CouponCode 中声明 EventBus 事件,而应在 EventBus.vue 中声明。
  • 现在,如果您删除 EventBus,您的 CouponCode.vue 测试就可以了——您应该只测试这里是否正确发出了事件,这个组件甚至不需要知道存在优惠券代码。在当前的情况下,您不需要对EventBus.vue. 进行单元测试,您还没有编写任何自定义逻辑来测试。a 集成测试将是测试CouponCode 发出事件的地方,EventBus 响应 - - 这不是 vue-test-utils 的用途,但 Nightwatch 或 Selenium 之类的东西最适合。
  • 嘿。我做了一个 Gist 来告诉你怎么做。 gist.github.com/lmiller1990/2c264448856853d139c10ca3c029df23 基本上,您要确保 EventBus 可以在没有其他组件的情况下进行测试。我所做的是创建一个函数,将$on 抽象为EventBus.js 本身,并自行测试EventBus。单元测试不应该需要其他组件,或者它不是单元测试。让我知道要点是否有帮助:)
  • ComponentA 通过EvtBus.$emit('foo', 10) 发出一个事件,任何其他组件通过EvtBus.$on('foo', ...) 对该事件作出反应。对我来说,似乎我并不关心测试 EvtBus,因为它只是以那种简单明了的方式工作。我们知道 Vue 实例会发出和监听事件。我关心的是知道我使用 EvtBus 从 ComponentA 发出了一个事件,然后在其他组件中,我关心我是否可以使用 EvtBus 监听此类传入事件。无论如何,这只是我目前对这种情况的理解,我很可能是完全错误的。
【解决方案4】:

我在使用 vue-test-utils 和 Jest 时遇到了同样的问题。对我来说,vue-test-utils 库的 createLocalVue() 解决了这个问题。此函数创建 Vue 的本地副本以在挂载组件时使用。在此 Vue 副本上安装插件可防止污染原始 Vue 副本。 (https://vue-test-utils.vuejs.org/api/options.html#localvue)

将此添加到您的测试文件将解决问题:

const EventBus = new Vue();

const GlobalPlugins = {
  install(v) {
    // Event bus
    v.prototype.$bus = EventBus;
  },
};

// create a local instance of the global bus
const localVue = createLocalVue();
localVue.use(GlobalPlugins);

【讨论】:

  • 您能详细说明如何使用它吗?您的答案与问题中的代码无关。我想使用wrp.vm.EvtBus.emitted('coupon-applied')之类的东西。
【解决方案5】:
jest.mock('@/main', () => ({
  $emit: jest.fn(),
}));

一开始就将其包含在规范文件的代码中。

注意:'@/main' 是您从中导入事件总线的文件。

【讨论】:

    猜你喜欢
    • 2019-10-24
    • 2021-11-26
    • 2019-04-17
    • 2019-07-16
    • 2018-11-24
    • 2021-02-17
    • 2018-11-09
    • 2018-09-28
    • 2021-04-03
    相关资源
    最近更新 更多