Giới thiệu Unit Test với Jest cho Vue component.
Hướng dẫn cài đặt Unit Test Jest
– npm i @vue/cli-plugin-unit-jest
– vue add unit-jest
"jest": { "moduleNameMapper": { "^vue$": "vue/dist/vue.common.js" }, "moduleFileExtensions": [ "js", "vue" ], "transform": { "^.+\\.js$": "/node_modules/babel-jest", ".*\\.(vue)$": "/node_modules/jest-vue-preprocessor" } }
Testing
Sau khi đã cài đặt được unit test jest vào dự án. Tại folder tests/unit có 1 file có tên là example.spec.js được viết sẵn để chúng ta có thể tham khảo.
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper.text()).toMatch(msg)
})
}
Đoạn code trên là một ví dụ đơn giản về cách sử dụng @vue/test-utils để thực hiện test trên 1 component vue .
1. Sử dụng phương thức shallowMount để tạo ra một wraper component mà không thực sự render đến các component con.
2. Chúng ta phải import component vue mà mà mình muốn test vào. <br>
3. `describe` là một hàm được cung cấp bởi jest để nhóm các test case liên quan đến một phần của ứng dụng.
4. `it` là một hàm được cung cấp bở jest để định nghĩa một test case. Test case này sẽ kiểm tra xem có đúng thông điệp mình muốn hay không. Cụ thể ở đây chúng ta kiểm tra giá trị prop data truyền vào có đúng mong đợi hay không.
5. `expect` là hàm được sử dụng để test một giá trị mong đợi từ một phần trong ứng dụng được test(ngoài ra còn có các hàm dùng để so sánh như: toBe, toEqual, toMatch, toContain, toBeInstanceOf…).
6. Chúng ta có thể chạy file test bằng cách chạy : $ npm run test: unit
Sử dụng với Vuex
Chúng ta sẽ xem qua cách import và sử dụng Vuex trong Unit Test :
import { mount, createLocalVue } from "@vue/test-utils"; import VueRouter from "vue-router"; import Vuex from "vuex"; import TopicCommentItem from "../../resources/js/admin/components/layouts/items/TopicCommentItem.vue"; import topicStore from '../../resources/js/admin/stores/topicStore'; const localVue = createLocalVue(); localVue.use(VueRouter); localVue.use(Vuex); jest.mock('axios', () => ({ post: jest.fn(() => Promise.resolve()) })) describe("TopicCommentItem", () => { let store; beforeEach(() => { store = new Vuex.Store({ modules: { topic: { namespaced: true, state: topicStore.state, actions: topicStore.actions, mutations: topicStore.mutations, } } }) }); const comment = { id: 3642, content: "xxx", dislike: 0, author_name: "44441", display_author_name: 0, like: 0, rely_comment: 0, date: "2023/01/13(金) 12:31:02", title: 'NHM Indonesia thừa nhận đội nhà "dưới trình" tuyển Việt Nam', status: 1, hash: "y12bviVaririfswM", url_info: [], img: "http://pandora.local.com/",
sort_number: 14, reply_comment_sort_number: null, }; const routes = [ { path: '/comments/list/:page?', component: TopicCommentItem, name: 'adminTopicCommentPage', meta: { title: 'コメント操作' }, }] it('should call the API with the correct parameters and data', async () => { const expectedParams = { arrId: 1, status: 2 }; // const returnValue = { data: { status: 'success', message: 'Status changed successfully' } }; const router = new VueRouter({ routes }); const wrapper = mount (TopicCommentItem, { localVue, router, store, propsData: { comment, page: 1, }, }); const commit = jest.fn(); await topicStore.actions.changeStatusComment({commit}, expectedParams); expect(commit).toHaveBeenCalledWith('SET_STATUS_MESSAGE', 'Status changed successfully'); expect(commit).toHaveBeenCalledWith('SET_STATUS', 'success'); const changeStatusCommentMock = jest.fn(() => Promise.resolve({ data: {} })); wrapper.vm.$store.dispatch = changeStatusCommentMock; await wrapper.vm.callApiStatus(expectedParams); expect(changeStatusCommentMock).toHaveBeenCalledWith('topic/changeStatusComment', expectedParams); }); it('test data vuex last update function component', async () => { const router = new VueRouter({ routes }); const wrapper = mount (TopicCommentItem, { localVue, router, store, propsData: { comment, arrComment: [], arrCommentHidden: [], }, data: function () { return { openModal: false, openModalDelete: false, openModalHidden: false, openModalUnHide: false, idComment: [], useUrl: 'http://pandora.local.com/', rateDislike: '', rateLike: '', like: 10, dislike: 20, }; }, }) wrapper.vm.commentSelected(5, 4); expect(store.state.topic.disableCheckBox).toEqual(1); expect(store.state.topic.disableButtonHidden).toEqual(false); }) });
const commit = jest.fn();
await topicStore.actions.changeStatusComment({commit}, expectedParams);
Hàm changeStatusComment được đặt ở file topicStore.
async changeStatusComment({commit}, params){
try {
let response = await axios.post(ApiConfig.API_ADMIN_V1+'comments/change', params);
commit('SET_STATUS_MESSAGE', 'Status changed successfully');
commit('SET_STATUS', 'success');
} catch(err) {
if (err.response.status == 401) {
commit("admin/SET_LOADING_LAYOUT", false, { root: true });
commit("LOGOUT");
}
}
},
Kiểm tra kết quả sau khi hàm được gọi đến có trả về đúng hay không:
expect(commit).toHaveBeenCalledWith('SET_STATUS_MESSAGE', 'Status changed successfully');
expect(commit).toHaveBeenCalledWith('SET_STATUS', 'success');
– Test case thứ 2 ta kiểm tra giá trị của disableCheckBox và disableButtonHidden trong Vuex store được cập nhật đúng hay không với :
Gọi đến hàm muốn test là commentSelected:
wrapper.vm.commentSelected(5, 4);// giá trị được truyền vào hàm.
Hàm commentSelected ở component TopicCommentItem.
commentSelected(id, status) {
if(status == 1) {
let arrComment = this.arrComment;
if (arrComment.includes(id)) {
let position = arrComment.indexOf(id);
arrComment.splice(position, 1);
} else {
arrComment.push(id);
}
if(typeof arrComment !== 'undefined' && arrComment.length > 0) {
this.$store.dispatch("topic/getDisableCheckBox", 2);
this.$store.dispatch("topic/getDisableButtonHide", false);
} else {
this.$store.dispatch("topic/getDisableCheckBox", 0);
this.$store.dispatch("topic/getDisableButtonHide", true);
}
} else {
let arrCommentHidden = this.arrCommentHidden;
if (arrCommentHidden.includes(id)) {
let position = arrCommentHidden.indexOf(id);
arrCommentHidden.splice(position, 1);
} else {
arrCommentHidden.push(id);
}
if(typeof arrCommentHidden !== 'undefined' && arrCommentHidden.length > 0) {
this.$store.dispatch("topic/getDisableCheckBox", 1);
this.$store.dispatch("topic/getDisableButtonHidden", false);
} else {
this.$store.dispatch("topic/getDisableCheckBox", 0);
this.$store.dispatch("topic/getDisableButtonHidden", true);
}
}
},
Kiểm tra kết quả các giá trị:
expect(store.state.topic.disableCheckBox).toEqual(1);
expect(store.state.topic.disableButtonHidden).toEqual(false);
Sau đó chúng ta chạy lệnh: npm run test:unit để kiểm tra kết quả.