Viết Unit Test dễ dàng hơn với phương pháp này

Nhìn qua về unit test Bài viết này phù hợp với những người đã biết về unit test, nên mình sẽ không giải thích các khái niệm căn bản nữa và sẽ đi thẳng vào những phần chính Unit test giống như chúng ta viết lại một lần component đấy, nhưng khi ứng dụng càng

Nhìn qua về unit test

image.png
Bài viết này phù hợp với những người đã biết về unit test, nên mình sẽ không giải thích các khái niệm căn bản nữa và sẽ đi thẳng vào những phần chính

Unit test giống như chúng ta viết lại một lần component đấy, nhưng khi ứng dụng càng lớn, nhiều thư viện được sử dụng như Vuex, Vue Router… nó sẽ khiên cho các file test trở nên rất dài, đôi khi còn dài hơn cả component cần test

Ví dụ về một component có sử dụng store và router như thế này

<!-- CreateTodo.vue --><script>exportdefault{
  name:'CreateTodo',
  computed:{user(){returnthis.$store.state.user
    }},
  methods:{submit(){this.$router.push({ name:'List'})}}}</script>

Khi tiến hành test chúng ta sẽ cần setup router và store để có thể test được component này

// CreateTodo.spec.jsimport{ shallowMount, createLocalVue }from'@vue/test-utils'import CreateTodo from'@/components/CreateTodo'import userModule from'@/store/modules/user'import VueRouter from'vue-router'import Vuex from'vuex'const localVue =createLocalVue()
localVue.use(VueRouter)
localVue.use(Vuex)// Tạo localVue sử dụng VueRouter và Vuexconst router =newVueRouter({
  routes:[{
      path:'/list',
      name:'List',component:()=>import('@/components/List.vue')}]})// Tạo router liên kết với component List.vueconst store =newVuex.Store({
  modules:{
    user: userModule
  }})// Tạo store với user modulelet wrapper
beforeEach(()=>{
  wrapper =shallowMount(CreateTodo,{
    localVue,
    router,
    store
  })// Tạo một wrapper từ shallowMount với router và store để sử dụng})afterEach(()=>{
  wrapper.destroy()})describe('Test CreateTodo',()=>{...})

Như vậy, khi có nhiều component sử dụng store và router, đều phải setup lại những phần router và store lặp đi lặp lại thế này, nên bài viết này mình sẽ cấu trúc lại để có thể viết test một cách ngắn ngọn và đơn giản hơn

Tổ chức thư mục

test
└── unit               
    ├── components
    |   └── CreateTodo.spec.js
    └── methods.js                 

Tạo các phương thức tái sử dụng

// methods.jsimport{ shallowMount, mount, createLocalVue }from'@vue/test-utils';// có ba thứ cần dùng là shallowMout, mount và createLocalVueimport Vuex from'vuex';import VueRouter from'vue-router';import{ routes }from'@/router/index';import user from'@/store/modules/user';// import routes và các module từ router và store của bạnexportconst localVue =createLocalVue();
localVue.use(VueRouter);
localVue.use(Vuex);// Tạo localVue sử dụng VueRouter và VuexexportconstcreateStore=(customModule ={})=>newVuex.Store({
  modules:{
    user,...customModule,},});// createStore đã được cấu hình sẵn với các module được import từ store của bạn // Có thể custom với giá trị đầu vào nhận một custom module có kiểu dữ liệu object, sẽ được merge với các module mặc địnhexportconstcreateRouter=(customRoute =[])=>newVueRouter({
  routes:[...routes,...customRoute
  ],});// createRouter đã được cấu hình sẵn với các routes được import từ router của bạn// Có thể custom với giá trị đầu vào nhận vào một custom route có kiểu dữ liệu array chứ các object routes, sẽ được merge với các routes mặc địnhexportconst createMock =((customMock ={})=>({// thêm các mock mặc định ở đây$t:()=>'',// ví dụ với i18n...customMock,}));// createMock với các mock mặc định và customMock dùng để customexportconst createStub =((customStub ={})=>({// thêm các stub mặc định ở đây'ButtonComponent':{ template:'<div></div>'},...customStub,}));// createStub với các stub mặc định và customStub dùng để customexportconstcreateShallow=(component, options ={})=>shallowMount(component,{
  stubs:createStub(),
  store:createStore(),
  router:createRouter(),
  mocks:createMock(),
  localVue,...options,});// Khởi tạo hàm createShallow, hàm này sẽ trả về hàm shallowMount của vue-test-utils// Nó đã được nhận vào sẵn các stubs, store, router, mocks từ các hàm bên trên// Và đầu vào sẽ nhận 2 giá trị là component và object chứa các options như shallowMount exportconstcreateDeep=(component, options ={})=>mount(component,{
  stubs:createStub(),
  store:createStore(),
  router:createRouter(),
  mocks:createMock(),
  localVue,...options,});// Tương tự như createShallow chỉ khác là sử dụng mount thay vì shallow

Hướng dẫn sử dụng

Và sau đó khi sử dụng chỉ cần dùng

import CreateTodo from'@/components/CreateTodo'import{ createShallow }from'../methods'let wrapper
beforeEach(()=>{
  wrapper =createShallow(CreateTodo)})afterEach(()=>{
  wrapper.destroy()})describe('Test CreateTodo',()=>{...})

So sánh với ví dụ ban đầu thì số lượng code đã được giảm thiểu rõ rệt

Từ giờ thay vì gọi hàm shallowMount từ vue-test-utils chúng ta chỉ cần sử dụng createShallow được viết sẵn từ file methods.js

wrapper được tạo từ createShallow đã có đầy đủ store, router, mock và stub…

Tùy chỉnh

Những trường hợp cần tùy chỉnh trong quá trình test sẽ xảy ra không nhiều. Mặc dù vậy, các function vẫn cho phép chúng ta sử dụng trong những trường hợp cần phải tùy chỉnh

Router

import{ createShallow, createRouter }from'../methods'import TodoComponent from'@/components/Todo'const customRouter =[{
    path:'/custom-router',
    name:'CustomRouter',component:()=>import('@/components/CustomRouter.vue')}]// Tạo một custom routerconst router =createRouter(customRouter)// createRouter() sẽ merge customRouter với các router hiện tạilet wrapper
beforeEach(()=>{
  wrapper =createShallow(TodoComponent,{
    router
  })// truyền options là router mới sẽ thay thế router cũ})

Store

import{ createShallow, createStore }from'../methods'import TodoComponent from'@/components/Todo'import todo from'@/store/modules/todo'const state ={
  todos:[{ title:"newTodo"}]}// Tạo state với dữ liệu mẫu để testconst store =createStore({
  todo:{...todo, 
    state 
  }})// Replace state và giữ lại getters, mutations, actions// createStore() sẽ merge các module vào vuexlet wrapper
beforeEach(()=>{
  wrapper =createShallow(TodoComponent,{
    store
  })// truyền options là store mới sẽ thay thế store cũ})

Mock

import{ createShallow, createMock }from'../methods'import TodoComponent from'@/components/Todo'const mocks =createMock({$t:()=>'',})// createMock() sẽ merge các mock mới với các mock mặc địnhlet wrapper
beforeEach(()=>{
  wrapper =createShallow(TodoComponent,{
    mocks
  })// truyền options là mocks mới sẽ thay thế mocks cũ})

Stub

import{ createShallow, createStub }from'../methods'import TodoComponent from'@/components/Todo'const stubs =createStub({'CustomStub':{ template:'<div></div>'},})// createStub() sẽ merge các stubs mới với các stubs mặc địnhlet wrapper
beforeEach(()=>{
  wrapper =createShallow(TodoComponent,{
    stubs
  })// truyền options là stubs mới sẽ thay thế stubs cũ})

Kết luận

Đây là một phương pháp giúp chúng ta giảm thiểu số lượng code cần viết mỗi khi test một các đơn giản và ngắn gọn

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ