简体   繁体   English

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

[英]How to write Jest unit test for a Vue form component which uses a Vuex store?

I have a login form. 我有一个登录表单。 When I fill out the login form with data and the login button is clicked: 当我用数据填写登录表单并单击登录按钮时:

  • form data (username, password) is sent to the server and a response is returned 表单数据(用户名,密码)将发送到服务器并返回响应
  • If the form data is invalid, a message is displayed by the <flash-message> component 如果表单数据无效,则<flash-message>组件将显示一条消息
  • If the form data is valid, the user is redirected to the dashboard 如果表单数据有效,则会将用户重定向到仪表板

Since this component heavily depends on the Vuex store, I'm unable to think of some valid test cases for this component. 由于此组件在很大程度上取决于Vuex存储,因此我无法考虑此组件的一些有效测试用例。

  • Is this component testable? 这个组件可以测试吗?
  • If it is testable, how do I write a unit test in jest? 如果它可测试的,我如何在开玩笑中编写单元测试?
  • Which part(s) of my component should I mock? 我应该模拟我组件的哪个部分?
  • Should I use the vue-test-utils mount/shallowMount methods to wrap my component? 我应该使用vue-test-utils mount / shallowMount方法来包装我的组件吗?
  • My component uses Bootstrap-Vue UI components. 我的组件使用Bootstrap-Vue UI组件。 How do I deal with them? 我该如何处理它们?

I don't have experience with JavaScript ecosystem, so a verbose explanation would be appreciated. 我没有JavaScript生态系统的经验,所以请大家详细解释。

Login.vue 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 says: Vue test utils文档说:

[W]e recommend writing tests that assert your component's public interface, and treat its internals as a black box. [W]建议编写断言组件公共接口的测试,并将其内部视为黑盒子。 A single test case would assert that some input (user interaction or change of props) provided to the component results in the expected output (render result or emitted custom events). 单个测试用例会断言提供给组件的某些输入(用户交互或道具更改)会产生预期的输出(渲染结果或发出的自定义事件)。

So we shouldn't be testing bootstrap-vue components, that's the job of that project's maintainers. 所以我们不应该测试bootstrap-vue组件,这是该项目维护者的工作。

Write code with unit tests in mind 编写代码时要考虑单元测试

To make it easier to test components, scoping them to their sole responsibility will help. 为了更容易测试组件,确定它们的全部责任范围将有所帮助。 Meaning that the login form should be its own SFC (single file component), and the login page is another SFC that uses the login form. 这意味着登录表单应该是它自己的SFC(单个文件组件),登录页面是另一个使用登录表单的SFC。

Here, we have the login form isolated from the login page. 在这里,我们从登录页面隔离了登录表单。

<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>

I removed the router from the store action dispatch as it's not the store responsibility to handle the redirection when the login succeeds or fails. 我从商店操作调度中删除了路由器,因为当登录成功或失败时,处理重定向不是商店的责任。 The store shouldn't have to know that there's a frontend in front of it. 商店不应该知道它前面有一个前端。 It deals with the data and async requests related to the data. 它处理与数据相关的数据和异步请求。

Test each part independently 独立测试每个部分

Test the store actions individually. 单独测试商店操作。 Then they can be mocked completely in components. 然后他们可以在组件中完全嘲笑。

Testing the store actions 测试商店操作

Here, we want to make sure the store does what it's meant to do. 在这里,我们希望确保商店能够完成它的目的。 So we can check that the state has the right data, that HTTP calls are made while mocking them. 因此,我们可以检查状态是否具有正确的数据,在模拟它们时进行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.
});

Testing our simple LoginForm 测试我们的简单LoginForm

The only real thing this component do is dispatching the login action when the submit button is called. 这个组件唯一真正的做法是在调用提交按钮时调度login操作。 So we should test this. 所以我们应该测试一下。 We don't need to test the action itself since it's already tested individually. 我们不需要测试动作本身,因为它已经单独测试过。

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();
    });
});

Testing the flash message component 测试Flash消息组件

In that same vein, we should mock the store state with injected messages and make sure that the FlashMessage component displays the messages correctly by testing the presence of each message items, the classes, etc. 同样,我们应该使用注入的消息模拟存储状态,并确保FlashMessage组件通过测试每个消息项,类等的存在来正确显示消息。

Testing the login page 测试登录页面

The login page component can now be just a container, so there's not much to test. 登录页面组件现在可以只是一个容器,所以没有太多要测试。

<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 何时使用mount vs shallow

The documentation on shallow says: 关于shallow文档说:

Like mount , it creates a Wrapper that contains the mounted and rendered Vue component, but with stubbed child components. mount一样,它创建一个Wrapper ,其中包含已安装和渲染的Vue组件,但包含已存根的子组件。

Meaning that child components from a container component will be replaced with <!-- --> comments and all their interactivity won't be there. 这意味着容器组件中的子组件将替换为<!-- -->注释,并且它们的所有交互性都不会存在。 So it isolates the component being tested from all the requirements its children may have. 因此,它将被测组件与其子项可能具有的所有要求隔离开来。

The inserted DOM of the login page would then be almost empty, where the FlashMessage , LoginForm and LoginNav components would be replaced: 然后,登录页面的插入DOM几乎为空,其中FlashMessageLoginFormLoginNav组件将被替换:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Vue,vuex:如何使用命名空间存储和 mocking 对组件进行单元测试? - Vue, vuex: how to unit test a component with namespaced store and mocking? 如何对依赖复杂Vuex存储并扩展另一个组件的Vue.js组件进行单元测试? - How do I unit test a Vue.js component that relies on a complicated Vuex store and extends another component? 我应该如何使用 Jest 为我的 Vue 组件编写测试? - How should I write a test for my Vue component using Jest? 如何为使用 jest 调用方法的组件编写测试用例 - How to write test case for a component which is calling a method using jest 如何使用Vue-test-utils和Jest测试Vuex变异 - How to test Vuex Mutations using Vue-test-utils and Jest Jest 测试在使用 react-hooks-form 和共享 refs 的 React 组件上失败 - Jest test fails on a react component which uses react-hooks-form with sharing refs 无法使用 Jest 在我的单元测试中呈现 Vue 组件 - Cannot render Vue component in my unit test with Jest 如何使用Jest为Promise编写代码进行单元测试 - How to write a unit test with Jest for code with Promise 如何使用 jest 为使用 uuid 的函数编写测试用例? - How do I write test case for a function which uses uuid using jest? 如何使用 Jest 测试 JSX Vue 组件 - How test JSX Vue component with Jest
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM