[英]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:
当我用数据填写登录表单并单击登录按钮时:
<flash-message>
component <flash-message>
组件将显示一条消息 Since this component heavily depends on the Vuex store, I'm unable to think of some valid test cases for this component. 由于此组件在很大程度上取决于Vuex存储,因此我无法考虑此组件的一些有效测试用例。
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组件,这是该项目维护者的工作。
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 the store actions individually. 单独测试商店操作。 Then they can be mocked completely in components.
然后他们可以在组件中完全嘲笑。
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.
});
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();
});
});
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
组件通过测试每个消息项,类等的存在来正确显示消息。
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>
mount
vs shallow
mount
vs shallow
The documentation on shallow
says: 关于
shallow
的文档说:
Like
mount
, it creates aWrapper
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几乎为空,其中
FlashMessage
, LoginForm
和LoginNav
组件将被替换:
<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.