如何使用 Mocha 在 Vue.js 中测试 DOM 更新?

How to test DOM update in Vue.js with Mocha?

我正在努力理解 Vue.js 使用 Karma、Mocha 和 Chai 进行单元测试的一些基本概念。

这是我的组件:

VueExample.vue

<template>
    <div>
        <p>{{ name }}</p>
        <input v-model="name">
    </div>
</template>

<script>
    export default {
        name: 'VueExample',
        data () {
            return {
                name: 'Bruce Lee'
            };
        }
    }
</script>

这就是我尝试测试它的方式:

VueExample.spec.js

import Vue from 'vue';
import VueExample from "../../../components/VueExample";

describe('VueExample.vue', () => {
    let vm;

    beforeEach(() => {
        const Constructor = Vue.extend(VueExample);
        vm = new Constructor().$mount();
    });

    it('should change the name', done => {
        const input = vm.$el.querySelector('input');
        input.value = 'Chuck Norris';
        expect(vm.$el.querySelector('p').textContent).to.equal('Bruce Lee');
        Vue.nextTick(() =>{
            expect(vm.$el.querySelector('p').textContent).to.equal('Chuck Norris');
            console.log(vm.$el.querySelector('p').textContent);
            done();
        });
    });
});

我使用 Karma 运行 测试和 Chai 作为断言库。 karma.conf.js 中的一切都已正确配置。当我 运行 这个测试时,它失败了。标签 <p> 内的文本不会改变。 console.log命令输出李小龙.

测试 运行 使用 Firefox。

更改输入的值不会触发 Vue 更新模型(因为输入的属性不是反应性的)。'

您可以在浏览器中尝试。 运行 在控制台中 document.getElementsByTagName('input')[0].value = 'Chuck Norris' 并没有任何反应,p 元素的文本仍然是 "Bruce Lee".

Vue的触发方式是input事件。所以你应该发送一个 input 事件。这可能是这样的:

let event = document.createEvent('HTMLEvents')
event.initEvent('input', true, true)
vm.$el.querySelector('input').dispatchEvent(event)

v-model relies on the input event,仅通过编辑 JavaScript 中的输入值不会发生这种情况。在 Vue 之外也是如此,如下所示:

function logInput(e) {
  console.log('input', e.target.value)
}

function update() {
  const input = document.querySelector('#input1')
  input.value = "foo"
}
<input oninput="logInput(event)" id="input1">
<button onclick="update()">Update textbox</button>
<div>Clicking button changes text but does not emit <code>input</code> event. See console.</div>

在您的测试中手动调度输入事件应该可行。在您的示例中,在设置 input.value:

之后执行 input.dispatchEvent(new Event('input'))
const input = vm.$el.querySelector('input');
input.value = 'Chuck Norris';
input.dispatchEvent(new Event('input')); // input event required to update v-model

@Anatoly 在评论之一中建议使用 Vue Test Utils。我在玩弄并想出了一个我想分享的解决方案:

yarn add -D @vue/test-utils

或:

npm install --save-dev @vue/test-utils

然后测试文件看起来像这样:

import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import VueExample from "../../../components/VueExample";

describe('VueExample.vue', () => {
  let wrapper;

  beforeEach(() => {
    wrapper = shallowMount(VueExample);
  });

  it('should change the name', done => {
    const textInput = wrapper.find('input');
    textInput.setValue('Chuck Norris');
    textInput.trigger('input');
    expect(wrapper.find('p').text()).to.equal('Bruce Lee');
    Vue.nextTick(() => {
      expect(wrapper.find('p').text()).to.equal('Chuck Norris');
      done();
    });
  });
});

此示例中只有一个测试,但我仍然使用 beforeEach 以使其更容易通过进一步测试进行扩展。这里我们挂载Vue组件VueExample。在测试中我们找到 <input> 标签,将其值设置为 Chuck Norris 并触发 input 事件。我们可以看到<p>标签的文本节点没有变化,仍然是李小龙。这是由于 Vue 中 DOM 更新的异步性质。

使用nextTick()我们可以检查chane已经生效并且<p>标签的文本节点等于之前设置的值,即Chuck Norris.