Test

Giới thiệu Unit Test vuejs

Giới thiệu Unit Test với Jest cho Vue component.

Vue-test-unit, một thư viện VueJS testing chính thức dựa trên avoriaz, vừa mới ra lò. Nó cung cấp tất cả các tool cần thiết giúp viết unit test trong ứng dụng VueJS một cách dễ dàng.

Hướng dẫn cài đặt Unit Test Jest

– npm i @vue/cli-plugin-unit-jest

– vue add unit-jest

Thêm cấu hình vào file package.json.
"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); }) });
Chúng ta sử dụng các thư viện :
 
– Vuex: Thư viện giúp tạo ra một Vuex store instance để sử dụng trong unit test.
– VueRouter: Thư viện tạo ra một Vue Router instance để sử dụng trong unit test.
Các hàm được sử dụng :
 
createLocalVue(): Tạo ra một instance của Vue cho phạm vi local, giúp tránh gây ảnh hưởng đến các thư viện Vue khác được sử dụng trong ứng dụng.
localVue.use(): Sử dụng một plugin cho instance Vue local, trong trường hợp này là VueRouter và Vuex.
mount(): Tạo ra một instance của Vue component và trả về một đối tượng VueWrapper để test.
jest.fn(): Tạo ra một mock function.
mock(): Mock một module hoặc một đối tượng.
beforeEach: là một hàm trong Jest, được sử dụng để thiết lập một trạng thái cần thiết cho các bài kiểm tra trước khi chúng được thực thi. Cụ thể ở đây là store được sử dụng để lưu trữ trạng thái của các chủ đề và các bình luận liên quan đến chúng.
Trong đoạn code có 2 test case:
 
– Test case đầu tiên sẽ kiểm tra xem hàm changeStatusComment có được gọi hay không và trả về kết quả đúng không :
Phương pháp mock và gọi hàm:
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 disableCheckBoxdisableButtonHidden  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ả.