如何为使用Vuex存储的Vue表单组件编写Jest单元测试?

sak*_*zai 4 javascript unit-testing vue.js jestjs

我有一个登录表格。当我用数据填写登录表单并单击登录按钮时:

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

由于此组件在很大程度上取决于Vuex存储,因此我无法想到该组件的一些有效测试用例。

  • 这个组件可以测试吗?
  • 如果它可检验的,我怎么写开玩笑单元测试?
  • 我应该模拟组件的哪一部分?
  • 我应该使用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>
Run Code Online (Sandbox Code Playgroud)

Emi*_*ron 13

Vue测试实用程序文档说:

[W]建议编写测试以声明组件的公共接口,并将其内部视为黑盒。一个测试用例将断言,提供给组件的某些输入(用户交互或道具更改)会导致预期的输出(渲染结果或发出的自定义事件)。

因此,我们不应该测试Bootstrap-Vue组件,那是该项目维护人员的工作。

在编写代码时要牢记单元测试

为了使测试组件更加容易,将它们的范围确定为自己的责任将有所帮助。这意味着登录表单应该是其自己的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>
Run Code Online (Sandbox Code Playgroud)

我将路由器从存储操作分派中删除了,因为登录成功或失败时,存储不是处理重定向的责任。商店不必知道前面有一个前端。它处理数据以及与数据相关的异步请求。

独立测试每个部分

分别测试存储操作。然后可以在组件中完全模拟它们。

测试商店动作

在这里,我们要确保商店做到了预定的目的。因此,我们可以检查状态是否具有正确的数据,并在模拟它们的同时进行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.
});
Run Code Online (Sandbox Code Playgroud)

测试我们简单的LoginForm

该组件所做的唯一真正的工作就是login在调用Submit按钮时调度操作。因此,我们应该对此进行测试。我们不需要测试动作本身,因为它已经被单独测试了。

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();
    });
});
Run Code Online (Sandbox Code Playgroud)

测试即时消息组件

同样,我们应该使用注入的消息来模拟存储状态,并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>
Run Code Online (Sandbox Code Playgroud)

何时使用mountvsshallow

上的文档shallow说:

像一样mount,它创建一个Wrapper,其中包含已安装和渲染的Vue组件,但具有残存的子组件。

这意味着容器组件中的子组件将被<!-- -->注释替换,并且它们之间的所有交互都将不存在。因此,它将被测组件与其子代可能具有的所有需求隔离开来。

然后登录页面的DOM插入会几乎是空的,其中FlashMessageLoginFormLoginNav组件将被替换:

<b-col sm="6" offset-sm="3">
    <h1><span class="fa fa-sign-in"></span> Login</h1>
    <!-- -->
    <!-- LOGIN FORM -->
    <!-- -->
    <hr>
    <!-- -->
</b-col>
Run Code Online (Sandbox Code Playgroud)