實際開發要先寫 test code 再寫 production code....
ProductApp.vue
先撰寫 ProductApp 的測試,在根目錄下找到 tests/unit 資料夾,在裡面新增 productApp.spec.js 檔案
測試程式使用 jest 的 shallowMount 幫助我們建立實體,因為其中包含 RouterView component,使用 stubs 模
擬 router-view
import { shallowMount } from '@vue/test-utils';
import ProductApp from '@/ProductApp';
describe('ProductApp.vue', () => {
it('renders a message', () => {
const wrapper = shallowMount(ProductApp, {
stubs: ['router-view'],
});
expect(wrapper.text()).toMatch('Product App!');
});
});
確認 ProductApp.vue 有成功掛載並確認文字內容顯示正確
開啟終端機輸入 npm run test:unit 來驗證測試是否通過
ProductList.vue
接著測試 ProductList.vue,在 ./tests/unit 底下新增 views 資料夾,並新增 ProductList.spec.js 檔案
分別測試:
(a)created 後的資料變動
由於在 ProductList 中使用到 productService 模組,可以使用 jest.mock 模擬該模組內容,並且建立模擬商
品資料
const mockProducts = [
{
id: 1,
name: 'Product I',
imgUrl: 'https://images.pexels.com/photos/1200458/pexels-photo-1200458.jpeg?
auto=compress&cs=tinysrgb&h=350',
desc: 'Description',
price: 20.00,
},
];
jest.mock('@/services/product-service', () => ({
get: () => mockProducts,
}));
也使用到 currency filter,因此需要註冊模擬的 currency filter
Vue.filter('currency', value => value);
最後測試內容如下
import { shallowMount } from '@vue/test-utils';
import ProductList from '@/views/ProductList';
import Vue from 'vue';
const mockProducts = [
{
id: 1,
name: 'Product I',
imgUrl: 'https://images.pexels.com/photos/1200458/pexels-photo-1200458.jpeg?
auto=compress&cs=tinysrgb&h=350',
desc: 'Description',
price: 20.00,
},
];
jest.mock('@/services/product-service', () => ({
get: () => mockProducts,
}));
Vue.filter('currency', value => value);
describe('ProductList.vue', () => {
it('sets the correct data', () => {
const wrapper = shallowMount(ProductList);
expect(wrapper.vm.products).toEqual(mockProducts);
});
});
(b)methods 內的方法
methods 內的 onProductClick 由於使用到 router,所以需要先模擬 router 模組,又因為 router 模組是在
main.js 時註冊,所以需要使用 localVue 來註冊
const localVue = createLocalVue();
localVue.use(VueRouter);
const routes = [
{
path: '/product/:id',
name: 'productInfo',
component: ProductInfo,
},
];
const router = new VueRouter({ routes });
最後測試 onProductClick 方法是否將路由導至 productInfo,由於商品項目是動態渲染的,需要透過
$nextTick() 的 callback 才有辦法取得渲染後的商品 element
const wrapper = shallowMount(ProductList, { localVue, router });
wrapper.vm.$nextTick(() => {
wrapper.find('.product-container').trigger('click');
expect(wrapper.vm.$route.name).toMatch('productInfo');
});
完整測試碼
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vue from 'vue';
import VueRouter from 'vue-router';
import ProductList from '@/views/ProductList';
import ProductInfo from '@/views/ProductInfo';
const mockProducts = [
{
id: 1,
name: 'Product I',
imgUrl: 'https://images.pexels.com/photos/1200458/pexels-photo-1200458.jpeg?
auto=compress&cs=tinysrgb&h=350',
desc: 'Description',
price: 20.00,
},
];
jest.mock('@/services/product-service', () => ({
get: () => mockProducts,
}));
Vue.filter('currency', value => value);
describe('ProductList.vue', () => {
it('sets the correct data', () => {
const wrapper = shallowMount(ProductList);
expect(wrapper.vm.products).toEqual(mockProducts);
});
it('trigger onProductClick method', () => {
const localVue = createLocalVue();
localVue.use(VueRouter);
const routes = [
{
path: '/product/:id',
name: 'productInfo',
component: ProductInfo,
},
];
const router = new VueRouter({ routes });
const wrapper = shallowMount(ProductList, { localVue, router });
wrapper.vm.$nextTick(() => {
wrapper.find('.product-container').trigger('click');
expect(wrapper.vm.$route.name).toMatch('productInfo');
});
});
});