我有一张登录表.当我用数据填写登录表单并单击登录按钮时:

  • 表单数据(用户名、密码)被发送到服务器,并发送响应
  • 如果表单数据无效,<flash-message>组件将显示一条消息
  • 如果表单数据有效,用户将被重定向到仪表板

由于该组件严重依赖Vuex存储,因此我无法为该组件想出一些有效的测试用例.

  • 这个组件可测试吗?
  • 如果我在一个测试单元里写is,我该怎么做?
  • 我应该模仿我的组件的哪些部分?
  • 我应该使用vue test utils mount/shallowMount方法包装我的组件吗?
  • 我的组件使用 bootstrap Vue UI组件.我该怎么对付他们?

我没有JavaScript生态系统方面的经验,所以请详细解释.

Login.vue

<template>
  <b-col sm="6" offset-sm="3">
    <h1><span class="fa fa-sign-in"></span> Login</h1>
    <flash-message></flash-message>
    <!-- LOGIN FORM -->
    <div class="form">
        <b-form-group>
            <label>Email</label>
            <input type="text" class="form-control" name="email" v-model="email">
        </b-form-group>

        <b-form-group>
            <label>Password</label>
            <input type="password" class="form-control" name="password" v-model="password">
        </b-form-group>

        <b-btn type="submit" variant="warning" size="lg" @click="login">Login</b-btn>
    </div>

    <hr>

    <p>Need an account? <b-link :to="{name:'signup'}">Signup</b-link></p>
    <p>Or go <b-link :to="{name:'home'}">home</b-link>.</p>
  </b-col>

</template>

<script>
export default {
  data () {
    return {
      email: '',
      password: ''
    }
  },
  methods: {
    async login () {
      this.$store.dispatch('login', {data: {email: this.email, password: this.password}, $router: this.$router})
    }
  }
}
</script>

推荐答案

Vue test utils documentation说:

[W] e建议编写断言组件公共接口的测试,并将其内部视为黑盒.单个测试用例将断言,提供给组件的一些输入(用户交互或props 的更改)会导致预期的输出(呈现结果或发出的自定义事件).

所以我们不应该测试 bootstrap vue组件,这是该项目维护人员的工作.

Write code with unit tests in mind

为了使测试组件更容易,将组件的范围限定在他们自己的责任范围内将有所帮助.这意味着登录表单应该是它自己的SFC(单文件组件),而登录页面是使用该登录表单的另一个SFC.

这里,我们将登录表单与登录页面隔离.

<template>
    <div class="form">
        <b-form-group>
            <label>Email</label>
            <input type="text" class="form-control" 
                   name="email" v-model="email">
        </b-form-group>

        <b-form-group>
            <label>Password</label>
            <input type="password" class="form-control" 
                   name="password" v-model="password">
        </b-form-group>

        <b-btn type="submit" variant="warning" 
               size="lg" @click="login">
               Login
        </b-btn>
    </div>
</template>

<script>
export default {
    data() {
        return { email: '', password: '' };
    },
    methods: {
        login() {
            this.$store.dispatch('login', {
                email: this.email,
                password: this.password
            }).then(() => { /* success */ }, () => { /* failure */ });
        }
    }
}
</script>

我从store action dispatch中删除了路由,因为当登录成功或失败时,处理重定向不是store的责任.store 不必知道前面有一个前端.它处理数据和与数据相关的异步请求.

Test each part independently

单独测试存储操作.然后可以在组件中完全模拟它们.

测试store 行为

在这里,我们想确保store 做它该做的事.因此,我们可以判断该州是否有正确的数据,在模拟HTTP调用时是否进行了HTTP调用.

import Vuex from 'vuex';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import storeConfig from '@/store/config';

describe('actions', () => {
    let http;
    let store;

    beforeAll(() => {
        http = new MockAdapter(axios);
        store = new Vuex.Store(storeConfig());
    });

    afterEach(() => {
        http.reset();
    });

    afterAll(() => {
        http.restore();
    });

    it('calls login and sets the flash messages', () => {
        const fakeData = { /* ... */ };
        http.onPost('api/login').reply(200, { data: fakeData });
        return store.dispatch('login')
            .then(() => expect(store.state.messages).toHaveLength(1));
    });
    // etc.
});

测试我们的简单登录表单

该组件唯一真正做的事情是在调用submit按钮时发送login个操作.所以我们应该测试一下.我们不需要测试动作本身,因为它已经单独测试过了.

import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
import LoginForm from '@/components/LoginForm';

const localVue = createLocalVue();
localVue.use(Vuex);

describe('Login form', () => {

    it('calls the login action correctly', () => {
        const loginMock = jest.fn(() => Promise.resolve());
        const store = new Vuex.Store({
            actions: {
                // mock function
                login: loginMock
            }
        });
        const wrapper = mount(LoginForm, { localVue, store });
        wrapper.find('button').trigger('click');
        expect(loginMock).toHaveBeenCalled();
    });
});

测试flash消息组件

同样,我们应该用注入的消息模拟存储状态,并通过测试每个消息项、类等的存在,确保FlashMessage组件正确显示消息.

测试登录页面

登录页面组件现在可以只是一个容器,所以没有什么需要测试的.

<template>
    <b-col sm="6" offset-sm="3">
        <h1><span class="fa fa-sign-in"></span> Login</h1>
        <flash-message />
        <!-- LOGIN FORM -->
        <login-form />
        <hr>
        <login-nav />
    </b-col>
</template>

<script>
import FlashMessage from '@/components/FlashMessage';
import LoginForm from '@/components/LoginForm';
import LoginNav from '@/components/LoginNav';

export default {
    components: {
        FlashMessage,
        LoginForm,
        LoginNav,
    }
}
</script>

When to use mount vs shallow

documentation on shallow人说:

mount类似,它创建了一个Wrapper,其中包含已装载和渲染的Vue组件,但带有存根子组件.

这意味着容器组件中的子组件将被替换为<!-- -->条注释,它们的所有交互性都将不存在.因此,它将被测试的组件与其子组件可能具有的所有要求隔离开来.

然后,DOM将被替换为FlashMessage个登录组件中的FlashMessage个:

<b-col sm="6" offset-sm="3">
    <h1><span class="fa fa-sign-in"></span> Login</h1>
    <!-- -->
    <!-- LOGIN FORM -->
    <!-- -->
    <hr>
    <!-- -->
</b-col>

Vue.js相关问答推荐

如何修复IntersectObserver导致我的OpenLayers Web应用程序中内存泄漏的问题?

当用户在嵌套路由中时激活链接

在计算(computed)属性上调用数组方法

有没有办法初始化一个保留初始 HTML 的 Vue 对象

Vue CLI - 编译后将配置文件保留为外部

指定一个 vue-cli vue.config.js 位置

Vue.js 开发工具未显示

在 vue 3 中使用 vue-chartjs:createElement 不是函数

Object.assign 没有正确复制

使用 vue-cli 遇到无法推断解析器错误

如何将 axios/axios 拦截器全局附加到 Nuxt?

vuex 中 { dispatch, commit } 的类型是什么?

在 vue/vuex(/flux?) 中使用 ES6 类是一种反模式吗?

如何将项目推送到 Vuejs 中数据对象的数组中? Vue 似乎没有关注 .push() 方法

使用 Vue-cli,我在哪里声明我的全局变量?

如何在 vuetify 中将工具提示添加到数据表标题?

如何使用vue.js/nuxt.js获取一个目录下的所有图片文件

VueJS错误避免直接改变props

用于 nuxt.js 构建的自定义 index.html

如何删除 vue cli 2?