用VUE测试库测试VUE组件的方法

438 阅读7分钟

测试是开发任何应用程序的关键。在测试阶段偷工减料可能会导致错误的自信,并最终导致错误的应用程序。

在本教程中,我们将演示如何使用VUE测试库。尽管我们可以使用无数其他测试库,但我支持以用户使用的方式测试您的应用程序,这正是Vue测试库的目标。

VUE测试库是测试库系列,包括Reaction测试库、DOM测试库等。它基于Vue的官方测试库Vue测试Utils和DOM测试库,这使得它能够使用测试库系列的特性。

先决条件

要遵循本教程,您应该有:

  • 节点>8.0.0和NPM>6安装在您的机器上
  • Vue CLI > 4 安装在你的机器上
  • 对VUE的基本认识
  • 对测试使用的基本理解玩笑

设置

由于我们将创建和测试单个文件组件,因此需要使用Vue CLI创建一个新的Vue项目。

运行下面的命令来设置一个新的Vue项目。

vue create testing-vue-components.

我们可以坚持默认的VUE配置,因为我们将自己安装任何附加的依赖项。

Vue测试库可以与许多测试框架一起使用,但我们将在本教程中使用JEST。

安装JEST和VUE测试库。

npm i --save-dev jest @testing-library/vue

因为我们将编写ES6,所以我们需要使用Babel来处理编译。Vue已经提供了Babel配置,所以我们只需要安装babel-jest并将JEST配置为转换.js使用Babel的文件。

我们做了一个定制的演示。点击这里查看.

npm i --save-dev babel-jest

同样,由于我们正在编写单个文件组件,因此需要配置Jest来加载.vue档案。这个vue-jest包裹能帮上忙。

npm install --save-dev vue-jest@4.0.0-beta.3

最后,更新package.json若要包含必要的配置,请执行以下操作。

"jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "vue"
    ],
    "transform": {
      "^.+\\.js$": "babel-jest",
      ".*\\.(vue)$": "vue-jest"
    }
  },

测试简单组件

当用户加载页面时,他们应该能够看到组件,所以首先要测试的应该是组件呈现。

考虑一下这个简单的Counter组件,它提供两个计数按钮并显示计数数:

// Counter.vue
<template>
  <div>
    <h3>Count: {{ counter }}</h3>
    <div class="button-container">
      <button @click="increment">Add</button>
      <button @click="decrement">Subtract</button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Counter',
  data() {
    return {
      counter: 0,
    };
  },
  methods: {
    increment() {
      this.counter++;
    },
    decrement() {
      this.counter--;
    },
  },
};
</script>

若要测试组件,请创建Counter.js文件中的tests目录。

import Counter from '../Counter.vue';
import { render } from '@testing-library/vue';
test('It renders correctly', () => {
  const { getByText } = render(Counter);
  getByText('Count: 0');
});

这个getByText助手检查文档中是否有带有指定参数的文本。在我们的计数器组件中,counter设置为0因此,如果不单击任何按钮,我们应该Count: 0呈现到文档中。

用户可能执行的其他操作包括单击两个按钮,因此我们也需要测试这些按钮。

import Counter from '../Counter.vue';
import { render, fireEvent } from '@testing-library/vue';

test('It correctly responds to button clicks', async () => {
  const { getByText } = render(Counter);
  // Check initial state
  getByText('Count: 0');

  // Get buttons.
  const addButton = getByText('Add');
  const subtractButton = getByText('Subtract');

  // Click the Add button.
  await fireEvent.click(addButton);
  // Counter should be updated.
  getByText('Count: 1');

  // Click the subtract button.
  await fireEvent.click(subtractButton);
  // Counter should be updated.
  getByText('Count: 0');
  // Further clicks
  await fireEvent.click(addButton);
  await fireEvent.click(addButton);
  await fireEvent.click(addButton);
  await fireEvent.click(addButton);
  getByText('Count: 4');
  await fireEvent.click(subtractButton);
  await fireEvent.click(subtractButton);
  getByText('Count: 2');
});

使用getByText,我们可以像普通用户一样获得按钮并单击它们。

顾名思义,fireEvent用于对元素分派不同的事件。我们在这里等待事件,因为Vue异步更新DOM,因此从fireEvent返回一个承诺,该承诺将在下一个滴答时得到解决。

测试表格元素

考虑下面的组件。它使用V模型存储设置双向数据绑定的内容,并在屏幕上的输入字段中显示用户键入的内容。

// Repeater.vue
<template>
  <section>
    <label for="item">Start Typing</label>
    <input type="text" v-model="item" id="item" />
    <div>You typed: {{ item }}</div>
  </section>
</template>
<script>
export default {
  data() {
    return {
      item: '',
    };
  },
};
</script>
<style></style>

要测试这个组件(用户将使用它),我们需要像用户一样思考。用户会来到页面,查看“开始键入”标签,并在输入字段中键入某些内容。用户希望输出显示在输出div中。我们的测试应该证明这一点。

import Repeater from '../Repeater.vue';
import { render, fireEvent } from '@testing-library/vue';

test('User can type and see output on the screen', async () => {
  const { getByLabelText, getByText, debug } = render(Repeater);
  // Get input associated with label.
  const input = getByLabelText('Start Typing');
  // Update the input field.
  await fireEvent.update(input, 'Sample text');
  // Assert that the update is successful.
  getByText('You typed: Sample text');

  // View the current state of the dom.
  debug();
});

使用getByLabelText方法,我们可以获得与特定标签相关联的输入字段。

为确保V模型正常工作,输入输入字段应显示在输出区域中。因此,我们应该用Sample text断言我们可以在DOM上找到它。

一些复杂的形式具有有效性,这些都是计算性质。让我们来看看如何用Vue测试库来测试那些。

// Form.vue

<template>
  <form action="">
    <label for="name">Name</label>
    <input type="text" name="name" id="name" v-model="name" />
    <label for="email">Email</label>
    <input type="email" name="email" id="email" v-model="email" />
    <button type="submit" :disabled="!hasValidFields">Submit</button>
  </form>
</template>
<script>
export default {
  name: 'AppForm',
  data() {
    return {
      name: '',
      email: '',
    };
  },
  computed: {
    hasValidFields() {
      return Boolean(this.email && this.name);
    },
  },
};
</script>
<style></style>

这个简单的表单组件通过确保不能单击Submit按钮来验证用户输入,除非两个字段都包含有效值。

和往常一样,为了测试这个组件,我们将像用户一样与它交互。

import Form from '../Form.vue';
import { render, fireEvent } from '@testing-library/vue';
test('User interaction with form', async () => {
  const { getByLabelText, getByText } = render(Form);
  const nameField = getByLabelText('Name');
  const emailField = getByLabelText('Email');
  const submitBtn = getByText('Submit');
  expect(submitBtn.disabled).toBe(true);
  // Update the name field.
  await fireEvent.update(nameField, 'James John');
  expect(submitBtn.disabled).toBe(true);
  // Add email.
  await fireEvent.update(emailField, 'james@example.com');
  expect(submitBtn.disabled).toBe(false);
});

正如我们的测试所显示的,提交按钮应该被禁用,直到提供了所有必需的字段。

测试复杂部件

您肯定会遇到更复杂的组件,这些组件依赖于外部数据,无论是通过道具还是通过Ajax请求获取数据。

道具和其他项(例如插槽)可以通过呈现方法的第二个参数传递给正在呈现的组件。

考虑一下这个输入组件,它将在整个应用程序中使用:

// AppInput.vue
<template>
  <div>
    <slot name="label"></slot>
    <input type="text" :name="name" :id="inputId" />
  </div>
</template>
<script>
export default {
  props: {
    name: {
      required: true,
      type: String,
    },
    inputId: {
      required: true,
      type: String,
    },
  },
};
</script>

对此的相应测试将是:

import AppInput from '../AppInput.vue';
import { render } from '@testing-library/vue';
test('It renders label', () => {
  const { getByLabelText } = render(AppInput, {
    props: {
      name: 'username',
      inputId: 'username',
    },
    slots: {
      label: `<label for="username">Enter Username</label>`,
    },
  });
  // Get input field by label text.
  getByLabelText('Enter Username');
});

调试测试

偶尔,测试会失败,重要的是要清楚地知道出了什么问题,以避免将来出现类似的错误。这就是Vue测试库真正闪耀的地方。

test('It renders label', () => {
  const { getByLabelText} = render(AppInput, {
    props: {
      name: 'username',
      inputId: 'username',
    },
    slots: {
      // Note wrong attribute value.
      label: `<label for="user">Enter Username</label>`,
    },
  });
  getByLabelText('Enter Username');
});

上述测试失败是因为getByLabelText找不到标签的关联输入字段。错误消息如下所示:

我们还可以在不同的时间点查看DOM的状态。让我们调试一下AppInput组件来查看呈现给DOM的内容。

import AppInput from '../AppInput.vue';
import { render } from '@testing-library/vue';
test('It renders label', () => {
  const { getByLabelText, debug } = render(AppInput, {
    props: {
      name: 'username',
      inputId: 'username',
    },
    slots: {
      label: `<label for="username">Enter Username</label>`,
    },
  });
  getByLabelText('Enter Username');

  // Logs a representation of the dom at this state.
  debug();
});

运行此测试将导致以下结果。

结语

在本教程中,我们演示了如何使用VUE测试库测试不同的组件。我希望我们讨论过的概念能激励你养成更好的测试习惯。最重要的是,我希望本教程能够说明在用户如何与组件交互的背景下测试组件的好处。

体验您的Vue应用程序,准确地了解用户的工作方式

调试Vue.js应用程序可能很困难,特别是当用户会话期间有几十个,如果不是数百个突变的话。如果您对监控和跟踪生产中所有用户的Vue突变感兴趣,请尝试LogRocket。

LogRocket就像一个用于Web应用程序的DVR,从字面上记录Vue应用程序中发生的一切,包括网络请求、JavaScript错误、性能问题等等。与猜测问题发生的原因不同,您可以聚合并报告应用程序在发生问题时所处的状态。

LogRocketVuex插件将Vuex突变记录到LogRocket控制台,为您提供了导致错误的原因以及应用程序在发生问题时所处的状态。