You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2021/01/20 01:35:20 UTC

[cloudstack-primate] 01/02: Explore Test Automation (#320)

This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch remove-merged-prs
in repository https://gitbox.apache.org/repos/asf/cloudstack-primate.git

commit 3c45af42771e5b8638e8e56712aa3bd0d8f17ca8
Author: Hoang Nguyen <ho...@unitech.vn>
AuthorDate: Wed Jan 20 07:42:25 2021 +0700

    Explore Test Automation (#320)
    
    * config jest and add setup for unittest
    
    * config jest coverage
    
    * example of unit testing a Status widget/component
    
    * add license for test file
    
    * add test/run command in the .travis.yml
    
    * add mock store and i18n for vue jest
    
    * add mock file missing
    
    * add mock router
    
    * add lincence to mock file & decodeHtml to setup file
    
    * add mock axios instance & fix eslint on tests folder
    
    * add test components > views > ActionButton component
    
    * fix for test coverage success
    
    * refactor test file
    
    * add test Views > Autogenview.vue (Navigation Guard, Watchers, Computed)
    
    * history mode mockRouter, refactor test code, test Autogenview > fetchData (routeName)
    
    * test Views > AutogenView.vue (processing 31%)
    
    * add mock router exception & test Views > AutogenView.vue (processing 43%)
    
    * test Views > AutogenView (processing 65%), add test onSearch, closeAction, execAction, listUuidOpts
    
    * refactor and add comment test files
    
    * test Views > AutogenView (processing 91%)
    
    * add comment file AutogenView.spec.js
    
    * test Views > AutogenView.vue (handleSubmit method)
---
 .babelrc                                        |    7 +
 .travis.yml                                     |    1 +
 babel.config.js                                 |   11 +-
 jest.config.js                                  |   19 +-
 package-lock.json                               |   63 +-
 package.json                                    |    4 +
 src/components/view/ActionButton.vue            |   14 +-
 src/views/compute/MigrateWizard.vue             |    8 +-
 tests/{unit => }/.eslintrc.js                   |    0
 tests/common/index.js                           |   88 +
 tests/{unit/.eslintrc.js => mock/mockAxios.js}  |   10 +-
 tests/{unit/.eslintrc.js => mock/mockI18n.js}   |   13 +-
 tests/mock/mockRouter.js                        |   64 +
 tests/{unit/.eslintrc.js => mock/mockStore.js}  |   34 +-
 tests/mockData/ActionButton.mock.json           |   30 +
 tests/mockData/AutogenView.mock.json            |  198 ++
 tests/mockData/MigrateWizard.mock.json          |   24 +
 tests/mockData/Status.mock.json                 |   38 +
 tests/{unit/.eslintrc.js => setup.js}           |   33 +-
 tests/unit/components/view/ActionButton.spec.js |  337 +++
 tests/unit/components/widgets/Status.spec.js    |  395 +++
 tests/unit/views/AutogenView.spec.js            | 2939 +++++++++++++++++++++++
 tests/unit/views/compute/MigrateWizard.spec.js  |  686 ++++++
 23 files changed, 4979 insertions(+), 37 deletions(-)

diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..39fd999
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,7 @@
+{
+  "env": {
+    "test": {
+      "plugins": ["require-context-hook"]
+    }
+  }
+}
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index b90555b..02a2c81 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,3 +22,4 @@ cache:
   npm: false
 script:
   - npm run lint && npm run build
+  - npm run test:unit
diff --git a/babel.config.js b/babel.config.js
index a5fbcb8..68a21db 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -15,10 +15,11 @@
 // specific language governing permissions and limitations
 // under the License.
 
-module.exports = {
+const babelConfig = {
   presets: [
     '@vue/app'
-  ]
+  ],
+  plugins: []
   // if your use import on Demand, Use this code
   // ,
   // plugins: [
@@ -29,3 +30,9 @@ module.exports = {
   //   } ]
   // ]
 }
+
+if (process.env.NODE_ENV === 'test') {
+  babelConfig.plugins.push('require-context-hook')
+}
+
+module.exports = babelConfig
diff --git a/jest.config.js b/jest.config.js
index 1c3356b..7fcb5df 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -16,6 +16,8 @@
 // under the License.
 
 module.exports = {
+  testURL: 'http://localhost/',
+  setupFiles: ['<rootDir>/tests/setup.js'],
   moduleFileExtensions: [
     'js',
     'jsx',
@@ -24,11 +26,13 @@ module.exports = {
   ],
   transform: {
     '^.+\\.vue$': 'vue-jest',
-    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
+    '.+\\.(css|styl|less|sass|scss|png|svg|jpg|ttf|woff|woff2)?$': 'jest-transform-stub',
     '^.+\\.jsx?$': 'babel-jest'
   },
   moduleNameMapper: {
-    '^@/(.*)$': '<rootDir>/src/$1'
+    '.+\\.svg?.+$': 'jest-transform-stub',
+    '^@/(.*)$': '<rootDir>/src/$1',
+    '^@public/(.*)$': '<rootDir>/public/$1'
   },
   snapshotSerializers: [
     'jest-serializer-vue'
@@ -36,5 +40,14 @@ module.exports = {
   testMatch: [
     '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
   ],
-  testURL: 'http://localhost/'
+  transformIgnorePatterns: [
+    '<rootDir>/node_modules/(?!ant-design-vue|vue)'
+  ],
+  collectCoverage: true,
+  collectCoverageFrom: [
+    '<rootDir>/src/**/*.{js,vue}',
+    '!**/node_modules/**',
+    '!<rootDir>/src/locales/*.{js, json}'
+  ],
+  coverageReporters: ['html', 'text-summary']
 }
diff --git a/package-lock.json b/package-lock.json
index 2b40790..00113b8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3803,6 +3803,7 @@
         "vue-loader": "^15.9.2",
         "vue-style-loader": "^4.1.2",
         "webpack": "^4.0.0",
+        "webpack-bundle-analyzer": "^3.8.0",
         "webpack-chain": "^6.4.0",
         "webpack-dev-server": "^3.11.0",
         "webpack-merge": "^4.2.2"
@@ -10190,9 +10191,9 @@
       }
     },
     "duplexer": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
-      "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
+      "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
       "dev": true
     },
     "duplexer3": {
@@ -18055,9 +18056,9 @@
       }
     },
     "opener": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz",
-      "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==",
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
+      "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
       "dev": true
     },
     "opn": {
@@ -24873,6 +24874,56 @@
         }
       }
     },
+    "webpack-bundle-analyzer": {
+      "version": "3.9.0",
+      "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz",
+      "integrity": "sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==",
+      "dev": true,
+      "requires": {
+        "acorn": "^7.1.1",
+        "acorn-walk": "^7.1.1",
+        "bfj": "^6.1.1",
+        "chalk": "^2.4.1",
+        "commander": "^2.18.0",
+        "ejs": "^2.6.1",
+        "express": "^4.16.3",
+        "filesize": "^3.6.1",
+        "gzip-size": "^5.0.0",
+        "lodash": "^4.17.19",
+        "mkdirp": "^0.5.1",
+        "opener": "^1.5.1",
+        "ws": "^6.0.0"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "7.4.1",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+          "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+          "dev": true
+        },
+        "acorn-walk": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
+          "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
+          "dev": true
+        },
+        "commander": {
+          "version": "2.20.3",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+          "dev": true
+        },
+        "mkdirp": {
+          "version": "0.5.5",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+          "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.5"
+          }
+        }
+      }
+    },
     "webpack-chain": {
       "version": "6.5.1",
       "resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.5.1.tgz",
diff --git a/package.json b/package.json
index afe12fb..4b9aab8 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
     "ant-design-vue": "~1.6.2",
     "antd-theme-webpack-plugin": "^1.3.4",
     "axios": "^0.19.2",
+    "babel-plugin-require-context-hook": "^1.0.0",
     "core-js": "^3.6.5",
     "enquire.js": "^2.1.6",
     "js-cookie": "^2.2.1",
@@ -153,6 +154,9 @@
       "autoprefixer": {}
     }
   },
+  "jest": {
+    "testEnvironment": "node"
+  },
   "browserslist": [
     "> 1%",
     "last 2 versions",
diff --git a/src/components/view/ActionButton.vue b/src/components/view/ActionButton.vue
index df36a16..570dae1 100644
--- a/src/components/view/ActionButton.vue
+++ b/src/components/view/ActionButton.vue
@@ -80,7 +80,7 @@ export default {
   },
   data () {
     return {
-      actionBadge: []
+      actionBadge: {}
     }
   },
   mounted () {
@@ -132,7 +132,7 @@ export default {
       this.$emit('exec-action', action)
     },
     handleShowBadge () {
-      const dataBadge = {}
+      this.actionBadge = {}
       const arrAsync = []
       const actionBadge = this.actions.filter(action => action.showBadge === true)
 
@@ -157,7 +157,7 @@ export default {
                 }
               }
 
-              if (json[responseJsonName].count && json[responseJsonName].count > 0) {
+              if (json[responseJsonName] && json[responseJsonName].count && json[responseJsonName].count > 0) {
                 response.count = json[responseJsonName].count
               }
 
@@ -170,12 +170,10 @@ export default {
 
         Promise.all(arrAsync).then(response => {
           for (let j = 0; j < response.length; j++) {
-            this.$set(dataBadge, response[j].api, {})
-            this.$set(dataBadge[response[j].api], 'badgeNum', response[j].count)
+            this.$set(this.actionBadge, response[j].api, {})
+            this.$set(this.actionBadge[response[j].api], 'badgeNum', response[j].count)
           }
-        })
-
-        this.actionBadge = dataBadge
+        }).catch(() => {})
       }
     }
   }
diff --git a/src/views/compute/MigrateWizard.vue b/src/views/compute/MigrateWizard.vue
index 87c4d63..46346e9 100644
--- a/src/views/compute/MigrateWizard.vue
+++ b/src/views/compute/MigrateWizard.vue
@@ -164,19 +164,19 @@ export default {
           jobId: jobid,
           successMessage: `${this.$t('message.success.migrating')} ${this.resource.name}`,
           successMethod: () => {
-            this.$parent.$parent.close()
+            this.$emit('close-action')
           },
           errorMessage: this.$t('message.migrating.failed'),
           errorMethod: () => {
-            this.$parent.$parent.close()
+            this.$emit('close-action')
           },
           loadingMessage: `${this.$t('message.migrating.processing')} ${this.resource.name}`,
           catchMessage: this.$t('error.fetching.async.job.result'),
           catchMethod: () => {
-            this.$parent.$parent.close()
+            this.$emit('close-action')
           }
         })
-        this.$parent.$parent.close()
+        this.$emit('close-action')
       }).catch(error => {
         this.$notification.error({
           message: this.$t('message.request.failed'),
diff --git a/tests/unit/.eslintrc.js b/tests/.eslintrc.js
similarity index 100%
copy from tests/unit/.eslintrc.js
copy to tests/.eslintrc.js
diff --git a/tests/common/index.js b/tests/common/index.js
new file mode 100644
index 0000000..544c66e
--- /dev/null
+++ b/tests/common/index.js
@@ -0,0 +1,88 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import mockI18n from '../mock/mockI18n'
+import mockStore from '../mock/mockStore'
+import mockRouter from '../mock/mockRouter'
+
+import localVue from '../setup'
+import { mount } from '@vue/test-utils'
+import { pollJobPlugin, notifierPlugin } from '@/utils/plugins'
+
+localVue.use(pollJobPlugin)
+localVue.use(notifierPlugin)
+
+function createMockRouter (newRoutes = []) {
+  let routes = []
+  if (!newRoutes || Object.keys(newRoutes).length === 0) {
+    return mockRouter.mock(routes)
+  }
+
+  routes = [...newRoutes]
+
+  return mockRouter.mock(routes)
+}
+
+function createMockI18n (locale = 'en', messages = {}) {
+  return mockI18n.mock(locale, messages)
+}
+
+function createMockStore (state = {}, actions = {}) {
+  return mockStore.mock(state, actions)
+}
+
+function decodeHtml (html) {
+  const text = document.createElement('textarea')
+  text.innerHTML = html
+
+  return text.value
+}
+
+function createFactory (component, options) {
+  var {
+    router = null,
+    i18n = null,
+    store = null,
+    props = {},
+    data = {},
+    mocks = {}
+  } = options
+
+  if (!router) router = createMockRouter()
+  if (!i18n) i18n = createMockI18n()
+  if (!store) store = createMockStore()
+
+  return mount(component, {
+    localVue,
+    router,
+    i18n,
+    store,
+    propsData: props,
+    mocks,
+    data () {
+      return { ...data }
+    }
+  })
+}
+
+export default {
+  createFactory,
+  createMockRouter,
+  createMockI18n,
+  createMockStore,
+  decodeHtml
+}
diff --git a/tests/unit/.eslintrc.js b/tests/mock/mockAxios.js
similarity index 86%
copy from tests/unit/.eslintrc.js
copy to tests/mock/mockAxios.js
index 7a26e36..9463ecf 100644
--- a/tests/unit/.eslintrc.js
+++ b/tests/mock/mockAxios.js
@@ -15,8 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
-module.exports = {
-  env: {
-    jest: true
-  }
-}
+const mockAxios = jest.genMockFromModule('axios')
+
+mockAxios.create = jest.fn(() => mockAxios)
+
+export default mockAxios
diff --git a/tests/unit/.eslintrc.js b/tests/mock/mockI18n.js
similarity index 80%
copy from tests/unit/.eslintrc.js
copy to tests/mock/mockI18n.js
index 7a26e36..869b6aa 100644
--- a/tests/unit/.eslintrc.js
+++ b/tests/mock/mockI18n.js
@@ -15,8 +15,15 @@
 // specific language governing permissions and limitations
 // under the License.
 
-module.exports = {
-  env: {
-    jest: true
+import VueI18n from 'vue-i18n'
+
+const mockI18n = {
+  mock: (locale = 'en', message = {}) => {
+    return new VueI18n({
+      locale: locale,
+      messages: message
+    })
   }
 }
+
+export default mockI18n
diff --git a/tests/mock/mockRouter.js b/tests/mock/mockRouter.js
new file mode 100644
index 0000000..2063934
--- /dev/null
+++ b/tests/mock/mockRouter.js
@@ -0,0 +1,64 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import VueRouter from 'vue-router'
+
+const mockRouter = {
+  routes: [
+    {
+      path: '/',
+      name: 'home',
+      meta: { icon: 'home' },
+      children: []
+    }
+  ],
+  mock: (routes = []) => {
+    mockRouter.routes[0].children = [
+      {
+        path: '/exception',
+        name: 'exception',
+        children: [
+          {
+            path: '/exception/403',
+            name: 403,
+            hidden: true,
+            meta: { icon: 'icon-error-test' }
+          },
+          {
+            path: '/exception/404',
+            name: 404,
+            hidden: true,
+            meta: { icon: 'icon-error-test' }
+          },
+          {
+            path: '/exception/500',
+            name: 500,
+            hidden: true,
+            meta: { icon: 'icon-error-test' }
+          }
+        ]
+      }
+    ]
+    if (routes && routes.length > 0) {
+      mockRouter.routes[0].children = [...mockRouter.routes[0].children, ...routes]
+    }
+
+    return new VueRouter({ routes: mockRouter.routes, mode: 'history' })
+  }
+}
+
+export default mockRouter
diff --git a/tests/unit/.eslintrc.js b/tests/mock/mockStore.js
similarity index 57%
copy from tests/unit/.eslintrc.js
copy to tests/mock/mockStore.js
index 7a26e36..6d94906 100644
--- a/tests/unit/.eslintrc.js
+++ b/tests/mock/mockStore.js
@@ -15,8 +15,36 @@
 // specific language governing permissions and limitations
 // under the License.
 
-module.exports = {
-  env: {
-    jest: true
+import Vuex from 'vuex'
+
+const mockStore = {
+  state: {},
+  mock: (state, actions) => {
+    mockStore.state = {
+      app: {
+        device: 'desktop'
+      },
+      user: {},
+      permission: {}
+    }
+
+    if (state && Object.keys(state).length > 0) {
+      mockStore.state = { ...mockStore.state, ...state }
+    }
+
+    if (!actions) {
+      actions = {}
+    }
+
+    return new Vuex.Store({
+      state: mockStore.state,
+      getters: {
+        apis: () => mockStore.state.user.apis,
+        userInfo: () => mockStore.state.user.info
+      },
+      actions
+    })
   }
 }
+
+export default mockStore
diff --git a/tests/mockData/ActionButton.mock.json b/tests/mockData/ActionButton.mock.json
new file mode 100644
index 0000000..c0934d0
--- /dev/null
+++ b/tests/mockData/ActionButton.mock.json
@@ -0,0 +1,30 @@
+{
+  "messages": {
+    "en": { "label.action": "action-en" },
+    "de": { "label.action": "action-de" }
+  },
+  "apis": {
+    "test-api-case-1": {},
+    "test-api-case-2": {},
+    "test-api-case-3": {},
+    "test-api-case-4": {},
+    "test-api-case-5": {},
+    "test-api-case-6": {}
+  },
+  "routes": [
+    {
+      "name": "testRouter1",
+      "path": "/test-router-1",
+      "meta": {
+        "name": "systemvm"
+      }
+    },
+    {
+      "name": "testRouter2",
+      "path": "/test-router-2",
+      "meta": {
+        "name": "test-name"
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/mockData/AutogenView.mock.json b/tests/mockData/AutogenView.mock.json
new file mode 100644
index 0000000..d1df58f
--- /dev/null
+++ b/tests/mockData/AutogenView.mock.json
@@ -0,0 +1,198 @@
+{
+  "messages": {
+    "en": {
+      "labelname": "test-name-en",
+      "displaytext": "description-en",
+      "label.column1": "column1-en",
+      "label.column2": "column2-en",
+      "label.column3": "column3-en",
+      "label.id": "uuid-en",
+      "label.name": "name-en",
+      "label.domainid": "domain-en",
+      "label.self": "self-en",
+      "label.all": "all-en",
+      "label.tags": "tags-en",
+      "label.account": "account-en",
+      "label.domainids": "domainids-en",
+      "label.keypair": "keypair-en",
+      "label.filterby": "filterby-en",
+      "label.refresh": "refresh-en",
+      "message.error.required.input": "required-en",
+      "message.error.select": "select-en",
+      "label.search": "search-en",
+      "label.quota.configuration": "quota-configuration-en",
+      "label.quota.value": "quota-value-en",
+      "label.quota.tariff.effectivedate": "quota-effectivedate-en",
+      "label.confirmpassword": "confirmpassword-en",
+      "label.confirmpassword.description": "confirmpassword-description-en",
+      "label.open.documentation": "open",
+      "label.metrics": "metrics",
+      "label.showing": "Showing",
+      "label.of": "of",
+      "label.items": "items",
+      "label.page": "page",
+      "label.view.console": "view-console-en",
+      "error.fetching.async.job.result": "Error encountered while fetching async job result",
+      "label.cancel": "cancel",
+      "label.ok": "ok"
+    },
+    "de": {
+      "labelname": "test-name-de",
+      "displaytext": "description-de",
+      "label.column1": "column1-de",
+      "label.column2": "column2-de",
+      "label.column3": "column3-de",
+      "label.id": "uuid-de",
+      "label.name": "name-de",
+      "label.domainid": "domain-de",
+      "label.self": "self-de",
+      "label.all": "all-de",
+      "label.tags": "tags-de",
+      "label.account": "account-de",
+      "label.domainids": "domainids-de",
+      "label.keypair": "keypair-de",
+      "label.filterby": "filterby-de",
+      "label.refresh": "refresh-de",
+      "message.error.required.input": "required-de",
+      "message.error.select": "select-de",
+      "label.search": "search-de",
+      "label.quota.configuration": "quota-configuration-de",
+      "label.quota.value": "quota-value-de",
+      "label.quota.tariff.effectivedate": "quota-effectivedate-de",
+      "label.confirmpassword": "confirmpassword-de",
+      "label.confirmpassword.description": "confirmpassword-description-de",
+      "label.open.documentation": "open",
+      "label.metrics": "metrics",
+      "label.showing": "Showing",
+      "label.of": "of",
+      "label.items": "items",
+      "label.page": "page",
+      "label.view.console": "view-console-de",
+      "error.fetching.async.job.result": "Error encountered while fetching async job result",
+      "label.cancel": "cancel",
+      "label.ok": "ok"
+    }
+  },
+  "apis": {
+    "testApiNameCase1": {
+      "params": {},
+      "response": []
+    },
+    "testApiNameCase2": {
+      "params": {},
+      "response": []
+    },
+    "testApiNameCase3": {
+      "params": {},
+      "response": []
+    },
+    "testApiNameCase4": {
+      "params": {},
+      "response": [
+        {
+          "name": "column2",
+          "type": "string"
+        },
+        {
+          "name": "column1",
+          "type": "string"
+        },
+        {
+          "name": "column3",
+          "type": "string"
+        }
+      ]
+    },
+    "testApiNameCase5": {
+      "params": [
+        {
+          "name": "column2",
+          "type": "string"
+        },
+        {
+          "name": "column1",
+          "type": "string"
+        },
+        {
+          "name": "column3",
+          "type": "string"
+        },
+        {
+          "name": "name",
+          "type": "string"
+        },
+        {
+          "name": "id",
+          "type": "string"
+        }
+      ],
+      "response": []
+    },
+    "testApiNameCase6": {
+      "params": [
+        {
+          "name": "id",
+          "type": "uuid"
+        },
+        {
+          "name": "tags",
+          "type": "list"
+        },
+        {
+          "name": "column1",
+          "type": "list"
+        },
+        {
+          "name": "column2",
+          "type": "string"
+        },
+        {
+          "name": "account",
+          "type": "string"
+        },
+        {
+          "name": "confirmpassword",
+          "type": "string"
+        }
+      ],
+      "response": []
+    },
+    "listTemplates": {
+      "params": {},
+      "response": []
+    },
+    "listIsos": {
+      "params": {},
+      "response": []
+    },
+    "listRoles": {
+      "params": {},
+      "response": []
+    },
+    "listHosts": {
+      "params": {},
+      "response": []
+    },
+    "listTestApiNames": {
+      "params": {},
+      "response": []
+    },
+    "createAccount": {
+      "params": {},
+      "response": []
+    },
+    "addAccountToProject": {
+      "params": {},
+      "response": []
+    },
+    "quotaEmailTemplateList": {
+      "params": {},
+      "response": []
+    }
+  },
+  "info": {
+    "roletype": "Normal",
+    "account": "test-account",
+    "domainid": "test-domain-id"
+  }
+}
\ No newline at end of file
diff --git a/tests/mockData/MigrateWizard.mock.json b/tests/mockData/MigrateWizard.mock.json
new file mode 100644
index 0000000..0c581f1
--- /dev/null
+++ b/tests/mockData/MigrateWizard.mock.json
@@ -0,0 +1,24 @@
+{
+  "messages": {
+    "en": {
+      "name": "name-en",
+      "Suitability": "Suitability-en",
+      "cpuused": "cpuused-en",
+      "memused": "memused-en",
+      "select": "select-en",
+      "ok": "ok-en",
+      "message.load.host.failed": "Failed to load hosts",
+      "message.migrating.vm.to.host.failed": "Failed to migrate VM to host"
+    },
+    "de": {
+      "name": "name-de",
+      "Suitability": "Suitability-de",
+      "cpuused": "cpuused-de",
+      "memused": "memused-de",
+      "select": "select-de",
+      "ok": "ok-de",
+      "message.load.host.failed": "Failed to load hosts",
+      "message.migrating.vm.to.host.failed": "Failed to migrate VM to host"
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/mockData/Status.mock.json b/tests/mockData/Status.mock.json
new file mode 100644
index 0000000..179cb63
--- /dev/null
+++ b/tests/mockData/Status.mock.json
@@ -0,0 +1,38 @@
+{
+  "messages": {
+    "en": {
+      "state.running": "Running",
+      "state.migrating": "Migrating",
+      "state.stopped": "Stopped",
+      "state.starting": "Starting",
+      "state.stopping": "Stopping",
+      "state.suspended": "Suspended",
+      "state.pending": "Pending",
+      "state.expunging": "Expunging",
+      "state.error": "Error",
+      "message.publicip.state.allocated": "Allocated",
+      "message.publicip.state.created": "Created",
+      "message.vmsnapshot.state.active": "Active",
+      "message.vm.state.active": "Active",
+      "message.volume.state.active": "Active",
+      "message.guestnetwork.state.active": "Active",
+      "message.publicip.state.active": "Active",
+      "Created": "Created",
+      "Active": "Active",
+      "Allocated": "Allocated",
+      "Error": "Error",
+      "Expunging": "Expunging",
+      "Suspended": "Suspended",
+      "Pending": "Pending",
+      "Running": "Running",
+      "Starting": "Starting",
+      "Another": "Another",
+      "Ready": "Ready",
+      "Disabled": "Disabled",
+      "Migrating": "Migrating",
+      "Stopping": "Stopping",
+      "Alert": "Alert",
+      "Stopped": "Stopped"
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/unit/.eslintrc.js b/tests/setup.js
similarity index 50%
rename from tests/unit/.eslintrc.js
rename to tests/setup.js
index 7a26e36..0f532ff 100644
--- a/tests/unit/.eslintrc.js
+++ b/tests/setup.js
@@ -15,8 +15,35 @@
 // specific language governing permissions and limitations
 // under the License.
 
-module.exports = {
-  env: {
-    jest: true
+import Vue from 'vue'
+import Vuex from 'vuex'
+import Antd from 'ant-design-vue'
+import VueRouter from 'vue-router'
+import VueI18n from 'vue-i18n'
+import VueStorage from 'vue-ls'
+import VueClipboard from 'vue-clipboard2'
+import config from '@/config/settings'
+import { createLocalVue } from '@vue/test-utils'
+import registerRequireContextHook from 'babel-plugin-require-context-hook/register'
+
+const localVue = createLocalVue()
+
+Vue.use(Antd)
+Vue.use(VueStorage, config.storageOptions)
+
+localVue.use(VueRouter)
+localVue.use(VueI18n)
+localVue.use(Vuex)
+localVue.use(VueClipboard)
+
+registerRequireContextHook()
+
+window.matchMedia = window.matchMedia || function () {
+  return {
+    matches: false,
+    addListener: function () {},
+    removeListener: function () {}
   }
 }
+
+module.exports = localVue
diff --git a/tests/unit/components/view/ActionButton.spec.js b/tests/unit/components/view/ActionButton.spec.js
new file mode 100644
index 0000000..db7bd81
--- /dev/null
+++ b/tests/unit/components/view/ActionButton.spec.js
@@ -0,0 +1,337 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import mockAxios from '../../../mock/mockAxios'
+import ActionButton from '@/components/view/ActionButton'
+import common from '../../../common'
+import mockData from '../../../mockData/ActionButton.mock.json'
+
+jest.mock('axios', () => mockAxios)
+
+let router, store, i18n
+const state = {
+  user: {
+    apis: mockData.apis
+  }
+}
+router = common.createMockRouter(mockData.routes)
+store = common.createMockStore(state)
+i18n = common.createMockI18n('en', mockData.messages)
+
+const factory = (opts = {}) => {
+  router = opts.router || router
+  store = opts.store || store
+  i18n = opts.i18n || i18n
+
+  return common.createFactory(ActionButton, {
+    router,
+    store,
+    i18n,
+    props: opts.props || {},
+    data: opts.data || {}
+  })
+}
+
+describe('Components > View > ActionButton.vue', () => {
+  beforeEach(() => {
+    jest.clearAllMocks()
+  })
+
+  describe('Template', () => {
+    it('Button action is show', () => {
+      const expected = '<i aria-label="icon: plus" class="anticon anticon-plus">'
+      const wrapper = factory()
+
+      wrapper.vm.$nextTick(() => {
+        const received = wrapper.html()
+
+        expect(received).not.toContain(expected)
+      })
+    })
+
+    it('Normal button action is show', () => {
+      const expected = '<i aria-label="icon: plus" class="anticon anticon-plus">'
+      const propsData = {
+        actions: [
+          {
+            label: 'label.action',
+            api: 'test-api-case-1',
+            showBadge: false,
+            icon: 'plus',
+            dataView: false,
+            listView: true
+          }
+        ],
+        dataView: false,
+        listView: true
+      }
+
+      const wrapper = factory({ props: propsData })
+
+      wrapper.vm.$nextTick(() => {
+        const received = wrapper.html()
+
+        expect(received).toContain(expected)
+      })
+    })
+
+    it('Badge button action is show', (done) => {
+      const expected = '<span class="button-action-badge ant-badge">'
+      const propsData = {
+        actions: [
+          {
+            label: 'label.action',
+            api: 'test-api-case-2',
+            showBadge: true,
+            icon: 'plus',
+            dataView: true
+          }
+        ],
+        dataView: true
+      }
+      const dataMock = {
+        testapinameresponse: {
+          count: 0,
+          testapiname: []
+        }
+      }
+
+      mockAxios.mockImplementation(() => Promise.resolve(dataMock))
+
+      const wrapper = factory({ props: propsData })
+
+      wrapper.vm.$nextTick(() => {
+        const wrapperHtml = wrapper.html()
+        const received = common.decodeHtml(wrapperHtml)
+
+        expect(received).toContain(expected)
+
+        done()
+      })
+    })
+  })
+
+  describe('Method', () => {
+    describe('handleShowBadge()', () => {
+      it('check the api is called and returned is not null', (done) => {
+        const postData = new URLSearchParams()
+        const expected = { 'test-api-case-3': { badgeNum: 2 } }
+        const dataMock = { testapinameresponse: { count: 2 } }
+        const propsData = {
+          actions: [
+            {
+              label: 'label.action',
+              api: 'test-api-case-3',
+              showBadge: true,
+              icon: 'plus',
+              dataView: true
+            }
+          ],
+          dataView: true
+        }
+
+        mockAxios.mockResolvedValue(dataMock)
+
+        const wrapper = factory({ props: propsData })
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            data: postData,
+            method: 'GET',
+            params: {
+              command: 'test-api-case-3',
+              response: 'json'
+            },
+            url: '/'
+          })
+          expect(wrapper.vm.actionBadge).toEqual(expected)
+
+          done()
+        })
+      })
+
+      it('check the api is called returned is null', (done) => {
+        const postData = new URLSearchParams()
+        const expected = { 'test-api-case-4': { badgeNum: 0 } }
+        const dataMock = { data: [] }
+        const propsData = {
+          actions: [
+            {
+              label: 'label.action',
+              api: 'test-api-case-4',
+              showBadge: true,
+              icon: 'plus',
+              dataView: true
+            }
+          ],
+          dataView: true
+        }
+
+        mockAxios.mockResolvedValue(dataMock)
+
+        const wrapper = factory({ props: propsData })
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            data: postData,
+            method: 'GET',
+            params: {
+              command: 'test-api-case-4',
+              response: 'json'
+            },
+            url: '/'
+          })
+          expect(wrapper.vm.actionBadge).toEqual(expected)
+
+          done()
+        })
+      })
+
+      it('check the api is called and throws error', (done) => {
+        const postData = new URLSearchParams()
+        const propsData = {
+          actions: [
+            {
+              label: 'label.action',
+              api: 'test-api-case-5',
+              showBadge: true,
+              icon: 'plus',
+              dataView: true
+            }
+          ],
+          dataView: true
+        }
+        const errorMessage = 'errMethodMessage'
+
+        mockAxios.mockImplementationOnce(() => Promise.reject(errorMessage))
+
+        const wrapper = factory({ props: propsData })
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            data: postData,
+            method: 'GET',
+            params: {
+              command: 'test-api-case-5',
+              response: 'json'
+            },
+            url: '/'
+          })
+          expect(wrapper.vm.actionBadge).toEqual({})
+
+          done()
+        })
+      })
+    })
+
+    describe('execAction()', () => {
+      it('check emitted events are executed', async () => {
+        const expected = {
+          icon: 'plus',
+          label: 'label.action',
+          api: 'test-api-case-6',
+          showBadge: false,
+          dataView: true,
+          resource: {
+            id: 'test-resource-id'
+          }
+        }
+        const propsData = {
+          actions: [
+            {
+              icon: 'plus',
+              label: 'label.action',
+              api: 'test-api-case-6',
+              showBadge: false,
+              dataView: true
+            }
+          ],
+          dataView: true,
+          resource: {
+            id: 'test-resource-id'
+          }
+        }
+
+        const wrapper = factory({ props: propsData })
+
+        await wrapper.find('button').trigger('click')
+        await wrapper.vm.$nextTick()
+
+        expect(wrapper.emitted()['exec-action'][0]).toEqual([expected])
+      })
+    })
+  })
+
+  describe('Watcher', () => {
+    describe('handleShowBadge()', () => {
+      it('check handleShowBadge() is not called with empty resource', async () => {
+        const wrapper = factory({
+          props: {
+            resource: {
+              id: 'test-resource-id'
+            }
+          }
+        })
+        const handleShowBadge = jest.spyOn(wrapper.vm, 'handleShowBadge')
+        wrapper.setProps({
+          resource: null
+        })
+        await wrapper.vm.$nextTick()
+        expect(handleShowBadge).not.toBeCalled()
+      })
+
+      it('check handleShowBadge() is not called with resource containing id null', async () => {
+        const wrapper = factory({
+          props: {
+            resource: {
+              id: 'test-resource-id'
+            }
+          }
+        })
+
+        const handleShowBadge = jest.spyOn(wrapper.vm, 'handleShowBadge')
+        wrapper.setProps({
+          resource: { id: null }
+        })
+        await wrapper.vm.$nextTick()
+        expect(handleShowBadge).not.toBeCalled()
+      })
+
+      it('check handleShowBadge() is not called with changed resource data', async () => {
+        const wrapper = factory({
+          props: {
+            resource: {
+              id: 'test-resource-id-1'
+            }
+          }
+        })
+
+        wrapper.setProps({
+          resource: {
+            id: 'test-resource-id-2'
+          }
+        })
+        const handleShowBadge = jest.spyOn(wrapper.vm, 'handleShowBadge')
+        await wrapper.vm.$nextTick()
+        expect(handleShowBadge).toHaveBeenCalledTimes(1)
+      })
+    })
+  })
+})
diff --git a/tests/unit/components/widgets/Status.spec.js b/tests/unit/components/widgets/Status.spec.js
new file mode 100644
index 0000000..a2de5ef
--- /dev/null
+++ b/tests/unit/components/widgets/Status.spec.js
@@ -0,0 +1,395 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import Status from '@/components/widgets/Status'
+import common from '../../../common'
+import mockData from '../../../mockData/Status.mock.json'
+
+let router, i18n
+
+router = common.createMockRouter()
+i18n = common.createMockI18n('en', mockData.messages)
+
+const factory = (opts = {}) => {
+  router = opts.router || router
+  i18n = opts.i18n || i18n
+
+  return common.createFactory(Status, {
+    router,
+    i18n,
+    props: opts.props || {},
+    data: opts.data || {}
+  })
+}
+
+describe('Components > Widgets > Status.vue', () => {
+  describe('Methods', () => {
+    describe('getText()', () => {
+      it('getText() is called and the value returned is null', () => {
+        const propsData = {
+          text: 'Running',
+          displayText: false
+        }
+
+        const wrapper = factory({ props: propsData })
+
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-text"></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getText() is called with state equal Running', () => {
+        const propsData = {
+          text: 'Running',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-text">Running</span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getText() is called with state equal Stopped', () => {
+        const propsData = {
+          text: 'Stopped',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-text">Stopped</span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getText() is called with state equal Starting', () => {
+        const propsData = {
+          text: 'Starting',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-text">Starting</span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getText() is called with state equal Stopping', () => {
+        const propsData = {
+          text: 'Stopping',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-text">Stopping</span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getText() is called with state equal Suspended', () => {
+        const propsData = {
+          text: 'Suspended',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-text">Suspended</span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getText() is called with state equal Pending', () => {
+        const propsData = {
+          text: 'Pending',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-text">Pending</span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getText() is called with state equal Expunging', () => {
+        const propsData = {
+          text: 'Expunging',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-text">Expunging</span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getText() is called with state equal Error', () => {
+        const propsData = {
+          text: 'Error',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-text">Error</span>'
+
+        expect(received).toContain(expected)
+      })
+    })
+
+    describe('getBadgeStatus()', () => {
+      it('getBadgeStatus() is called and the value returned is default status', () => {
+        const propsData = {
+          text: 'Another',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-dot ant-badge-status-default"></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getBadgeStatus() is called and the value returned is success status', () => {
+        const propsData = {
+          text: 'Active',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-dot ant-badge-status-success"></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getBadgeStatus() is called and the value returned is error status', () => {
+        const propsData = {
+          text: 'Disabled',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-dot ant-badge-status-error"></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getBadgeStatus() is called and the value returned is processing status', () => {
+        const propsData = {
+          text: 'Migrating',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge-status-dot ant-badge-status-processing"></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getBadgeStatus() is called and the value returned is error status', () => {
+        const propsData = {
+          text: 'Alert',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-error"></span><span class="ant-badge-status-text">Alert</span></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getBadgeStatus() is called and the value returned is warning status with state equal Allocated', () => {
+        const propsData = {
+          text: 'Allocated',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-warning"></span><span class="ant-badge-status-text">Allocated</span></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getBadgeStatus() is called and the value returned is success status with state equal Allocated', () => {
+        const propsData = {
+          text: 'Allocated',
+          displayText: true
+        }
+
+        router = common.createMockRouter([{
+          name: 'testRouter1',
+          path: '/publicip',
+          meta: {
+            icon: 'test-router-1'
+          }
+        }])
+        router.push({ name: 'testRouter1' })
+
+        const wrapper = factory({ router: router, props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Allocated</span></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getBadgeStatus() is called and the value returned is warning status with state equal Created', () => {
+        const propsData = {
+          text: 'Created',
+          displayText: true
+        }
+
+        const wrapper = factory({ props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-warning"></span><span class="ant-badge-status-text">Created</span></span>'
+
+        expect(received).toContain(expected)
+      })
+    })
+
+    describe('getTooltip()', () => {
+      it('getTooltip() is called with `$route.path` equal `/vmsnapshot`', () => {
+        const propsData = {
+          text: 'Active',
+          displayText: true
+        }
+
+        router = common.createMockRouter([{
+          name: 'testRouter1',
+          path: '/vmsnapshot',
+          meta: {
+            icon: 'test-router-1'
+          }
+        }])
+        router.push({ name: 'testRouter1' })
+
+        const wrapper = factory({ router: router, props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Active</span></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getTooltip() is called with `$route.path` equal `/vm`', () => {
+        const propsData = {
+          text: 'Active',
+          displayText: true
+        }
+
+        router = common.createMockRouter([{
+          name: 'testRouter1',
+          path: '/vm',
+          meta: {
+            icon: 'test-router-1'
+          }
+        }])
+        router.push({ name: 'testRouter1' })
+
+        const wrapper = factory({ router: router, props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Active</span></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getTooltip() is called with `$route.path` equal `/volume`', () => {
+        const propsData = {
+          text: 'Active',
+          displayText: true
+        }
+
+        router = common.createMockRouter([{
+          name: 'testRouter1',
+          path: '/volume',
+          meta: {
+            icon: 'test-router-1'
+          }
+        }])
+        router.push({ name: 'testRouter1' })
+
+        const wrapper = factory({ router: router, props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Active</span></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getTooltip() is called with `$route.path` equal `/guestnetwork`', () => {
+        const propsData = {
+          text: 'Active',
+          displayText: true
+        }
+
+        router = common.createMockRouter([{
+          name: 'testRouter1',
+          path: '/guestnetwork',
+          meta: {
+            icon: 'test-router-1'
+          }
+        }])
+        router.push({ name: 'testRouter1' })
+
+        const wrapper = factory({ router: router, props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Active</span></span>'
+
+        expect(received).toContain(expected)
+      })
+
+      it('getTooltip() is called with `$route.path` equal `/publicip`', () => {
+        const propsData = {
+          text: 'Active',
+          displayText: true
+        }
+
+        router = common.createMockRouter([{
+          name: 'testRouter1',
+          path: '/publicip',
+          meta: {
+            icon: 'test-router-1'
+          }
+        }])
+        router.push({ name: 'testRouter1' })
+
+        const wrapper = factory({ router: router, props: propsData })
+        const received = wrapper.html()
+        const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Active</span></span>'
+
+        expect(received).toContain(expected)
+      })
+    })
+  })
+})
diff --git a/tests/unit/views/AutogenView.spec.js b/tests/unit/views/AutogenView.spec.js
new file mode 100644
index 0000000..66c347b
--- /dev/null
+++ b/tests/unit/views/AutogenView.spec.js
@@ -0,0 +1,2939 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import mockAxios from '../../mock/mockAxios'
+import AutogenView from '@/views/AutogenView'
+import user from '@/store/modules/user'
+import common from '../../common'
+import mockData from '../../mockData/AutogenView.mock.json'
+
+jest.mock('axios', () => mockAxios)
+user.state.apis = mockData.apis
+
+let router, store, i18n, mocks
+
+const state = {
+  user: {
+    apis: mockData.apis,
+    info: mockData.info
+  }
+}
+
+store = common.createMockStore(state)
+i18n = common.createMockI18n('en', mockData.messages)
+
+const actions = {
+  AddAsyncJob: jest.fn((jobId) => {})
+}
+const spyConsole = {
+  log: null,
+  warn: null
+}
+mocks = {
+  $notifyError: jest.fn((error) => {
+    return error
+  }),
+  $notification: {
+    info: jest.fn((option) => {
+      return {
+        message: option.message,
+        description: 'test-description',
+        duration: option.duration
+      }
+    }),
+    success: jest.fn((option) => {
+      return {
+        message: option.message,
+        description: option.description
+      }
+    })
+  },
+  $message: {
+    success: jest.fn((obj) => {
+      return obj
+    })
+  }
+}
+
+const factory = (opts = {}) => {
+  router = opts.router || router
+  i18n = opts.i18n || i18n
+  store = opts.store || store
+  mocks = opts.mocks || mocks
+
+  return common.createFactory(AutogenView, {
+    router,
+    i18n,
+    store,
+    mocks,
+    props: opts.props || {},
+    data: opts.data || {}
+  })
+}
+
+describe('Views > AutogenView.vue', () => {
+  let wrapper
+
+  beforeEach(() => {
+    jest.clearAllMocks()
+
+    if (wrapper) wrapper.destroy()
+    if (router && router.currentRoute.name !== 'home') {
+      router.replace({ name: 'home' })
+    }
+    state.user.info.roletype = 'Normal'
+    if (i18n.locale !== 'en') i18n.locale = 'en'
+    if (spyConsole.log) {
+      spyConsole.log.mockClear()
+      spyConsole.log.mockRestore()
+    }
+    if (spyConsole.warn) {
+      spyConsole.warn.mockClear()
+      spyConsole.warn.mockRestore()
+    }
+  })
+
+  describe('Navigation Guard', () => {
+    it('check beforeRouteUpdate() is called', () => {
+      router = common.createMockRouter([{
+        name: 'testRouter1',
+        path: '/test-router-1',
+        meta: {
+          icon: 'test-router-1'
+        }
+      }])
+      wrapper = factory({ router: router })
+      router.push({ name: 'testRouter1' })
+
+      const beforeRouteUpdate = wrapper.vm.$options.beforeRouteUpdate
+      const nextFun = jest.fn()
+
+      beforeRouteUpdate[0].call(wrapper.vm, {}, {}, nextFun)
+
+      wrapper.vm.$nextTick(() => {
+        expect(wrapper.vm.currentPath).toEqual('/test-router-1')
+        expect(nextFun).toHaveBeenCalled()
+      })
+    })
+
+    it('check beforeRouteLeave() is called', () => {
+      router = common.createMockRouter([{
+        name: 'testRouter1',
+        path: '/test-router-1',
+        meta: {
+          icon: 'test-router-1'
+        }
+      }])
+      wrapper = factory({ router: router })
+      router.push({ name: 'testRouter1' })
+
+      const beforeRouteLeave = wrapper.vm.$options.beforeRouteLeave
+      const nextFun = jest.fn()
+
+      beforeRouteLeave[0].call(wrapper.vm, {}, {}, nextFun)
+
+      wrapper.vm.$nextTick(() => {
+        expect(wrapper.vm.currentPath).toEqual('/test-router-1')
+        expect(nextFun).toHaveBeenCalled()
+      })
+    })
+  })
+
+  describe('Watchers', () => {
+    describe('$route', () => {
+      it('The wrapper data does not change when $router do not change', () => {
+        wrapper = factory()
+
+        const spy = jest.spyOn(wrapper.vm, 'fetchData')
+
+        wrapper.setData({
+          page: 2,
+          itemCount: 10
+        })
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.page).toEqual(2)
+          expect(wrapper.vm.itemCount).toEqual(10)
+          expect(spy).not.toBeCalled()
+        })
+      })
+
+      it('The wrapper data changes when $router changes', () => {
+        router = common.createMockRouter([{
+          name: 'testRouter2',
+          path: '/test-router-2',
+          meta: {
+            icon: 'test-router-2'
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        const spy = jest.spyOn(wrapper.vm, 'fetchData')
+
+        wrapper.setData({
+          page: 2,
+          itemCount: 10
+        })
+
+        router.push({ name: 'testRouter2' })
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.page).toEqual(1)
+          expect(wrapper.vm.itemCount).toEqual(0)
+          expect(spy).toBeCalled()
+        })
+      })
+    })
+
+    describe('$i18n.locale', () => {
+      it('Test language and fetchData() when not changing locale', () => {
+        wrapper = factory()
+
+        const spy = jest.spyOn(wrapper.vm, 'fetchData')
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.$t('labelname')).toEqual('test-name-en')
+          expect(spy).not.toBeCalled()
+        })
+      })
+
+      it('Test languages and fetchData() when changing locale', async () => {
+        wrapper = factory()
+
+        i18n.locale = 'de'
+        const spy = jest.spyOn(wrapper.vm, 'fetchData')
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.$t('labelname')).toEqual('test-name-de')
+          expect(spy).toBeCalled()
+        })
+      })
+    })
+  })
+
+  describe('Methods', () => {
+    describe('fetchData()', () => {
+      it('check routeName when fetchData() is called with $route.name is not empty', () => {
+        router = common.createMockRouter([{
+          name: 'testRouter1',
+          path: '/test-router-1',
+          meta: {
+            icon: 'test-router-1'
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'testRouter1' })
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.routeName).toEqual('testRouter1')
+          expect(wrapper.vm.items).toEqual([])
+        })
+      })
+
+      it('check routeName when fetchData() is called with $route.name is empty', () => {
+        router = common.createMockRouter([{
+          path: '/test-router-3',
+          meta: {
+            icon: 'test-router-3'
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        router.replace('/test-router-3')
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.routeName).toEqual('home')
+        })
+      })
+
+      it('check resource, dataView when fetchData() is called with $route.meta.params is not empty', () => {
+        router = common.createMockRouter([{
+          name: 'testRouter4',
+          path: '/test-router-4/:id',
+          meta: {
+            icon: 'test-router-4'
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'testRouter4', params: { id: 'test-id' } })
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.resource).toEqual({})
+          expect(wrapper.vm.dataView).toBeTruthy()
+        })
+      })
+
+      it('check columnKeys, actions when fetchData() is called with $route.meta.actions, route.meta.columns is not empty', () => {
+        router = common.createMockRouter([{
+          name: 'testRouter5',
+          path: '/test-router-5',
+          meta: {
+            icon: 'test-router-5',
+            permission: ['testApiNameCase1'],
+            columns: ['column1', 'column2', 'column3'],
+            actions: [
+              {
+                name: 'labelname',
+                icon: 'plus',
+                listView: true
+              }
+            ]
+          }
+        }])
+        wrapper = factory({ router: router })
+        const mockData = {
+          testapinamecase1response: {
+            count: 0,
+            testapinamecase1: []
+          }
+        }
+
+        mockAxios.mockImplementation(() => Promise.resolve(mockData))
+        router.push({ name: 'testRouter5' })
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.columnKeys.length).toEqual(3)
+          expect(wrapper.vm.actions.length).toEqual(1)
+          expect(wrapper.vm.columnKeys).toEqual(['column1', 'column2', 'column3'])
+          expect(wrapper.vm.actions).toEqual([{
+            name: 'labelname',
+            icon: 'plus',
+            listView: true
+          }])
+        })
+      })
+
+      it('check columnKeys assign by store.getters.apis when fetchData() is called', () => {
+        router = common.createMockRouter([{
+          name: 'testRouter6',
+          path: 'test-router-6',
+          meta: {
+            icon: 'test-router-6',
+            permission: ['testApiNameCase4']
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        const mockData = {
+          testapinamecase4response: {
+            count: 0,
+            testapinamecase4: []
+          }
+        }
+
+        mockAxios.mockImplementation(() => Promise.resolve(mockData))
+        router.push({ name: 'testRouter6' })
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.columnKeys.length).toEqual(3)
+          expect(wrapper.vm.columnKeys).toEqual(['column1', 'column2', 'column3'])
+        })
+      })
+
+      it('check columnKeys assign by $route.meta.columns when fetchData() is called', () => {
+        router = common.createMockRouter([{
+          name: 'testRouter7',
+          path: 'test-router-7',
+          meta: {
+            icon: 'test-router-7',
+            permission: ['testApiNameCase1'],
+            columns: [{ name: 'string' }]
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        const mockData = {
+          testapinamecase1response: {
+            count: 0,
+            testapinamecase1: []
+          }
+        }
+
+        mockAxios.mockImplementation(() => Promise.resolve(mockData))
+        router.push({ name: 'testRouter7' })
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.columns.length).toEqual(1)
+          expect(wrapper.vm.columns[0].title).toEqual('name-en')
+          expect(wrapper.vm.columns[0].dataIndex).toEqual('name')
+          expect(wrapper.vm.columns[0].scopedSlots).toEqual({ customRender: 'name' })
+          expect(typeof wrapper.vm.columns[0].sorter).toBe('function')
+        })
+      })
+
+      it('check api is called with params assign by $route.query', (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter8',
+          path: '/test-router-8',
+          meta: {
+            icon: 'test-router-8',
+            permission: ['testApiNameCase2']
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        const postData = new URLSearchParams()
+        const mockData = {
+          testapinamecase2response: {
+            count: 0,
+            testapinamecase2: []
+          }
+        }
+
+        mockAxios.mockImplementation(() => Promise.resolve(mockData))
+        router.push({ name: 'testRouter8', query: { key: 'test-value' } })
+
+        wrapper.vm.$nextTick(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            data: postData,
+            method: 'GET',
+            params: {
+              command: 'testApiNameCase2',
+              listall: true,
+              key: 'test-value',
+              page: 1,
+              pagesize: 20,
+              response: 'json'
+            },
+            url: '/'
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called with params assign by $route.meta.params', (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter9',
+          path: '/test-router-9',
+          meta: {
+            icon: 'test-router-9',
+            permission: ['testApiNameCase3'],
+            params: {
+              key: 'test-value'
+            }
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        const postData = new URLSearchParams()
+        const mockData = {
+          testapinamecase3response: {
+            count: 0,
+            testapinamecase3: []
+          }
+        }
+
+        mockAxios.mockImplementation(() => Promise.resolve(mockData))
+        router.push({ name: 'testRouter9' })
+
+        wrapper.vm.$nextTick(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            data: postData,
+            method: 'GET',
+            params: {
+              command: 'testApiNameCase3',
+              listall: true,
+              key: 'test-value',
+              page: 1,
+              pagesize: 20,
+              response: 'json'
+            },
+            url: '/'
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called with params has item id, name when $route.path startWith /ssh/', (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter17',
+          path: '/ssh/:id',
+          meta: {
+            icon: 'test-router-17',
+            permission: ['testApiNameCase1']
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        const mockData = {
+          testapinamecase1response: {
+            count: 0,
+            testapinamecase1: []
+          }
+        }
+
+        router.push({ name: 'testRouter17', params: { id: 'test-id' } })
+        mockAxios.mockResolvedValue(mockData)
+
+        wrapper.vm.$nextTick(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              listall: true,
+              id: 'test-id',
+              name: 'test-id',
+              page: 1,
+              pagesize: 20,
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called with params has item id, hostname when $route.path startWith /ldapsetting/', (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter18',
+          path: '/ldapsetting/:id',
+          meta: {
+            icon: 'test-router-18',
+            permission: ['testApiNameCase1']
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        const mockData = {
+          testapinamecase1response: {
+            count: 0,
+            testapinamecase1: []
+          }
+        }
+
+        router.push({ name: 'testRouter18', params: { id: 'test-id' } })
+        mockAxios.mockResolvedValue(mockData)
+
+        wrapper.vm.$nextTick(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              listall: true,
+              id: 'test-id',
+              hostname: 'test-id',
+              page: 1,
+              pagesize: 20,
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check items, resource when api is called with result is not empty', (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter19',
+          path: '/templates',
+          meta: {
+            icon: 'test-router-19',
+            permission: ['listTemplates']
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        const mockData = {
+          listtemplatesresponse: {
+            count: 2,
+            templates: [{
+              id: 'uuid1',
+              templateid: 'templateid-1',
+              name: 'template-test-1'
+            }, {
+              id: 'uuid2',
+              templateid: 'templateid-2',
+              name: 'template-test-2'
+            }]
+          }
+        }
+
+        router.push({ name: 'testRouter19' })
+        mockAxios.mockResolvedValue(mockData)
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenLastCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'listTemplates',
+              listall: true,
+              page: 1,
+              pagesize: 20,
+              response: 'json'
+            }
+          })
+
+          expect(wrapper.vm.items.length).toEqual(2)
+          expect(wrapper.vm.items).toEqual([
+            {
+              id: 'uuid1',
+              templateid: 'templateid-1',
+              name: 'template-test-1',
+              key: 0
+            },
+            {
+              id: 'uuid2',
+              templateid: 'templateid-2',
+              name: 'template-test-2',
+              key: 1
+            }
+          ])
+          expect(wrapper.vm.resource).toEqual({
+            id: 'uuid1',
+            templateid: 'templateid-1',
+            name: 'template-test-1',
+            key: 0
+          })
+
+          done()
+        })
+      })
+
+      it('check items, resource when api is called and $route.meta.columns has function', (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter20',
+          path: '/test-router-20',
+          meta: {
+            icon: 'test-router-20',
+            permission: ['testApiNameCase1'],
+            columns: [
+              'id',
+              'name',
+              {
+                column1: (record) => {
+                  return record.name
+                }
+              }
+            ]
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        const mockData = {
+          testapinamecase1response: {
+            count: 1,
+            testapinamecase1: [{
+              id: 'test-id',
+              name: 'test-name-value'
+            }]
+          }
+        }
+
+        router.push({ name: 'testRouter20' })
+        mockAxios.mockResolvedValue(mockData)
+
+        setTimeout(() => {
+          expect(wrapper.vm.items).toEqual([{
+            id: 'test-id',
+            name: 'test-name-value',
+            key: 0,
+            column1: 'test-name-value'
+          }])
+          expect(wrapper.vm.resource).toEqual({
+            id: 'test-id',
+            name: 'test-name-value',
+            key: 0,
+            column1: 'test-name-value'
+          })
+
+          done()
+        })
+      })
+
+      it('check items, resource when api is called and $route.path startWith /ssh', (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter21',
+          path: '/ssh',
+          meta: {
+            icon: 'test-router-21',
+            permission: ['testApiNameCase1']
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        const mockData = {
+          testapinamecase1response: {
+            count: 1,
+            testapinamecase1: [{
+              name: 'test-name-value'
+            }]
+          }
+        }
+
+        router.push({ name: 'testRouter21' })
+        mockAxios.mockResolvedValue(mockData)
+
+        setTimeout(() => {
+          expect(wrapper.vm.items).toEqual([{
+            id: 'test-name-value',
+            name: 'test-name-value',
+            key: 0
+          }])
+          expect(wrapper.vm.resource).toEqual({
+            id: 'test-name-value',
+            name: 'test-name-value',
+            key: 0
+          })
+
+          done()
+        })
+      })
+
+      it('check items, resource when api is called and $route.path startWith /ldapsetting', (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter22',
+          path: '/ldapsetting',
+          meta: {
+            icon: 'test-router-22',
+            permission: ['testApiNameCase1']
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        const mockData = {
+          testapinamecase1response: {
+            count: 1,
+            testapinamecase1: [{
+              name: 'test-name-value',
+              hostname: 'test-hostname-value'
+            }]
+          }
+        }
+
+        router.push({ name: 'testRouter22' })
+        mockAxios.mockResolvedValue(mockData)
+
+        setTimeout(() => {
+          expect(wrapper.vm.items).toEqual([{
+            id: 'test-hostname-value',
+            name: 'test-name-value',
+            hostname: 'test-hostname-value',
+            key: 0
+          }])
+          expect(wrapper.vm.resource).toEqual({
+            id: 'test-hostname-value',
+            name: 'test-name-value',
+            hostname: 'test-hostname-value',
+            key: 0
+          })
+
+          done()
+        })
+      })
+
+      it('check $notifyError is called when api is called with throw error', (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter22',
+          path: '/test-router-22',
+          meta: {
+            icon: 'test-router-22',
+            permission: ['testApiNameCase1']
+          }
+        }])
+
+        wrapper = factory({ router: router })
+
+        const errorMock = {
+          response: {},
+          message: 'Error: throw exception error'
+        }
+        router.push({ name: 'testRouter22' })
+        mockAxios.mockRejectedValue(errorMock)
+
+        setTimeout(() => {
+          expect(mocks.$notifyError).toHaveBeenCalledTimes(1)
+          expect(mocks.$notifyError).toHaveBeenCalledWith(errorMock)
+          done()
+        })
+      })
+
+      it('check $notifyError is called and router path = /exception/404 when api is called with throw error', (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter23',
+          path: '/test-router-23',
+          meta: {
+            icon: 'test-router-23',
+            permission: ['testApiNameCase1']
+          }
+        }])
+
+        wrapper = factory({ router: router })
+
+        const errorMock = {
+          response: {
+            status: 430
+          },
+          message: 'Error: Request Header Fields Too Large'
+        }
+        router.push({ name: 'testRouter23' })
+        mockAxios.mockRejectedValue(errorMock)
+
+        setTimeout(() => {
+          expect(mocks.$notifyError).toHaveBeenCalledTimes(1)
+          expect(mocks.$notifyError).toHaveBeenCalledWith(errorMock)
+          expect(router.currentRoute.path).toEqual('/exception/404')
+
+          done()
+        })
+      })
+
+      it('check $notifyError is called and router path = /exception/500 when api is called with throw error', (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter23',
+          path: '/test-router-23',
+          meta: {
+            icon: 'test-router-23',
+            permission: ['testApiNameCase1']
+          }
+        }])
+
+        wrapper = factory({ router: router })
+
+        const errorMock = {
+          response: {
+            status: 530
+          },
+          message: 'Error: Site is frozen'
+        }
+        router.push({ name: 'testRouter23' })
+        mockAxios.mockRejectedValue(errorMock)
+
+        setTimeout(() => {
+          expect(mocks.$notifyError).toHaveBeenCalledTimes(1)
+          expect(mocks.$notifyError).toHaveBeenCalledWith(errorMock)
+          expect(router.currentRoute.path).toEqual('/exception/500')
+
+          done()
+        })
+      })
+    })
+
+    describe('onSearch()', () => {
+      it('check fetchData() is called when onSearch() is called', async () => {
+        router = common.createMockRouter([{
+          name: 'testRouter24',
+          path: '/test-router-24',
+          meta: {
+            icon: 'test-router-24'
+          }
+        }])
+        wrapper = factory({ router: router })
+        router.push({ name: 'testRouter24', query: { page: 1, pagesize: 20 } })
+        const spy = jest.spyOn(wrapper.vm, 'fetchData')
+
+        await wrapper.vm.$nextTick()
+        wrapper.vm.onSearch()
+        expect(spy).toHaveBeenCalled()
+      })
+
+      it('check onSearch() is called with searchParams have item', async () => {
+        router = common.createMockRouter([{
+          name: 'testRouter24',
+          path: '/test-router-24',
+          meta: {
+            icon: 'test-router-24'
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'testRouter24' })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.onSearch()
+        expect(router.currentRoute.query).toEqual({
+          page: 1,
+          pagesize: 20
+        })
+      })
+
+      it('check onSearch() is called with searchQuery not in opts', async () => {
+        router = common.createMockRouter([{
+          name: 'testRouter25',
+          path: '/test-router-25',
+          meta: {
+            icon: 'test-router-25'
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'testRouter25' })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.onSearch({
+          key1: 'key1-value'
+        })
+        expect(router.currentRoute.query).toEqual({
+          key1: 'key1-value',
+          page: 1,
+          pagesize: 20
+        })
+      })
+
+      it('check onSearch() is called with searchQuery in opts but this is empty', async () => {
+        router = common.createMockRouter([{
+          name: 'testRouter26',
+          path: '/test-router-26',
+          meta: {
+            icon: 'test-router-26'
+          },
+          query: {}
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'testRouter26' })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.onSearch({
+          searchQuery: null
+        })
+        expect(router.currentRoute.query).toEqual({
+          page: 1,
+          pagesize: 20
+        })
+      })
+
+      it('check onSearch() is called with searchQuery in opts', async () => {
+        router = common.createMockRouter([{
+          name: 'testRouter26',
+          path: '/test-router-26',
+          meta: {
+            icon: 'test-router-26'
+          },
+          query: {}
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'testRouter26' })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.onSearch({
+          searchQuery: 'test-query'
+        })
+        expect(router.currentRoute.query).toEqual({
+          keyword: 'test-query',
+          q: 'test-query',
+          page: 1,
+          pagesize: 20
+        })
+      })
+
+      it('check onSearch() is called with searchQuery in opts and route.name equal `role`', async () => {
+        router = common.createMockRouter([{
+          name: 'role',
+          path: '/test-router-26',
+          meta: {
+            icon: 'test-router-26'
+          },
+          query: {}
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'role' })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.onSearch({
+          searchQuery: 'test-query'
+        })
+        expect(router.currentRoute.query).toEqual({
+          name: 'test-query',
+          q: 'test-query',
+          page: 1,
+          pagesize: 20
+        })
+      })
+
+      it('check onSearch() is called with searchQuery in opts and route.name equal `templatetype`', async () => {
+        router = common.createMockRouter([{
+          name: 'quotaemailtemplate',
+          path: '/test-router-26',
+          meta: {
+            icon: 'test-router-26'
+          },
+          query: {}
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'quotaemailtemplate' })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.onSearch({
+          searchQuery: 'test-query'
+        })
+        expect(router.currentRoute.query).toEqual({
+          templatetype: 'test-query',
+          q: 'test-query',
+          page: 1,
+          pagesize: 20
+        })
+      })
+
+      it('check onSearch() is called with searchQuery in opts and route.name equal `globalsetting`', async () => {
+        router = common.createMockRouter([{
+          name: 'globalsetting',
+          path: '/test-router-26',
+          meta: {
+            icon: 'test-router-26'
+          },
+          query: {}
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'globalsetting' })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.onSearch({
+          searchQuery: 'test-query'
+        })
+        expect(router.currentRoute.query).toEqual({
+          name: 'test-query',
+          q: 'test-query',
+          page: 1,
+          pagesize: 20
+        })
+      })
+    })
+
+    describe('closeAction()', () => {
+      it('check currentAction, showAction when closeAction() is called', () => {
+        const data = {
+          currentAction: {
+            loading: true
+          },
+          showAction: true
+        }
+        wrapper = factory({ data: data })
+
+        expect(wrapper.vm.currentAction).toEqual({ loading: true })
+        expect(wrapper.vm.showAction).toBeTruthy()
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.closeAction()
+
+          expect(wrapper.vm.currentAction).toEqual({})
+          expect(wrapper.vm.showAction).toBeFalsy()
+        })
+      })
+    })
+
+    describe('execAction()', () => {
+      it('check showAction, actionData and router name when execAction() is called', () => {
+        router = common.createMockRouter([{
+          name: 'testRouter26',
+          path: '/test-router-26',
+          meta: {
+            icon: 'test-router-26'
+          }
+        }])
+        const data = {
+          actionData: {
+            name: 'test-add-action'
+          }
+        }
+        wrapper = factory({ router: router, data: data })
+
+        expect(router.currentRoute.name).toEqual('home')
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.execAction({
+            label: 'labelname',
+            icon: 'plus',
+            component: () => jest.fn(),
+            api: 'testRouter26',
+            popup: false
+          })
+
+          expect(wrapper.vm.showAction).toBeFalsy()
+          expect(router.currentRoute.name).toEqual('testRouter26')
+        })
+      })
+
+      it('check currentAction params and paramsField when execAction() is called', () => {
+        wrapper = factory()
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.execAction({
+            api: 'testApiNameCase5'
+          })
+
+          expect(wrapper.vm.currentAction.params).toEqual([
+            { name: 'id', type: 'string' },
+            { name: 'name', type: 'string' },
+            { name: 'column1', type: 'string' },
+            { name: 'column2', type: 'string' },
+            { name: 'column3', type: 'string' }
+          ])
+          expect(wrapper.vm.currentAction.paramFields).toEqual([])
+          expect(wrapper.vm.showAction).toBeTruthy()
+        })
+      })
+
+      it('check currentAction params and paramsField when execAction() is called with args is exists', () => {
+        wrapper = factory()
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.execAction({
+            api: 'testApiNameCase5',
+            args: ['column1', 'column2', 'column3']
+          })
+
+          expect(wrapper.vm.currentAction.params).toEqual([
+            { name: 'column1', type: 'string' },
+            { name: 'column2', type: 'string' },
+            { name: 'column3', type: 'string' },
+            { name: 'name', type: 'string' },
+            { name: 'id', type: 'string' }
+          ])
+          expect(wrapper.vm.currentAction.paramFields).toEqual([
+            { name: 'column1', type: 'string' },
+            { name: 'column2', type: 'string' },
+            { name: 'column3', type: 'string' }
+          ])
+          expect(wrapper.vm.showAction).toBeTruthy()
+        })
+      })
+
+      it('check currentAction params and paramsField when execAction() is called with args is function', () => {
+        wrapper = factory()
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.execAction({
+            api: 'testApiNameCase5',
+            resource: { id: 'test-id-value', name: 'test-name-value' },
+            args: (record, store) => {
+              return ['Admin'].includes(store.userInfo.roletype) ? ['column1', 'column2', 'column3'] : ['id', 'name']
+            }
+          })
+
+          expect(wrapper.vm.currentAction.params).toEqual([
+            { name: 'id', type: 'string' },
+            { name: 'name', type: 'string' },
+            { name: 'column1', type: 'string' },
+            { name: 'column2', type: 'string' },
+            { name: 'column3', type: 'string' }
+          ])
+          expect(wrapper.vm.currentAction.paramFields).toEqual([
+            { name: 'id', type: 'string' },
+            { name: 'name', type: 'string' }
+          ])
+          expect(wrapper.vm.showAction).toBeTruthy()
+        })
+      })
+
+      it('check currentAction paramsField and listUuidOpts() is called when execAction() is called', () => {
+        wrapper = factory()
+        const spy = jest.spyOn(wrapper.vm, 'listUuidOpts')
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.execAction({
+            api: 'testApiNameCase6',
+            args: ['id', 'tags', 'column1', 'column2', 'account'],
+            mapping: {
+              column2: () => {
+                return 'test-value'
+              }
+            }
+          })
+
+          expect(wrapper.vm.currentAction.paramFields).toEqual([
+            { name: 'id', type: 'uuid' },
+            { name: 'tags', type: 'string' },
+            { name: 'column1', type: 'list' },
+            { name: 'column2', type: 'string' },
+            { name: 'account', type: 'string' }
+          ])
+          expect(wrapper.vm.showAction).toBeTruthy()
+          expect(spy).toHaveBeenCalled()
+          expect(spy).toHaveBeenCalledWith({ name: 'id', type: 'uuid' })
+          expect(spy).toHaveBeenCalledWith({ name: 'column1', type: 'list' })
+          expect(spy).toHaveBeenCalledWith({ name: 'column2', type: 'string' })
+          expect(spy).toHaveBeenCalledWith({ name: 'account', type: 'string' })
+        })
+      })
+
+      it('check fillEditFormFieldValues() is called when execAction() is called', () => {
+        wrapper = factory()
+        const spy = jest.spyOn(wrapper.vm, 'fillEditFormFieldValues')
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.execAction({
+            api: 'testApiNameCase6',
+            dataView: true,
+            icon: 'edit'
+          })
+
+          expect(spy).toHaveBeenCalled()
+        })
+      })
+
+      it('check currentAction paramFields when execAction() is called args has confirmpassword field', () => {
+        wrapper = factory()
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.execAction({
+            api: 'testApiNameCase6',
+            args: ['confirmpassword'],
+            mapping: {}
+          })
+
+          expect(wrapper.vm.currentAction.paramFields).toEqual([
+            { name: 'confirmpassword', type: 'password', required: true, description: 'confirmpassword-description-en' }
+          ])
+        })
+      })
+    })
+
+    describe('listUuidOpts()', () => {
+      it('check api not called when listUuidOpts() is called with currentAction.mapping.id is null', (done) => {
+        wrapper = factory({
+          data: {
+            currentAction: {
+              mapping: {
+                id: () => { return '' }
+              }
+            }
+          }
+        })
+
+        wrapper.vm.listUuidOpts({ name: 'id', type: 'uuid' })
+
+        setTimeout(() => {
+          expect(mockAxios).not.toHaveBeenCalled()
+          done()
+        })
+      })
+
+      it('check api not called when listUuidOpts() is called with currentAction.mapping is empty', (done) => {
+        wrapper = factory({
+          data: {
+            currentAction: {
+              mapping: {}
+            }
+          }
+        })
+
+        wrapper.vm.listUuidOpts({ name: 'test-name', type: 'uuid' })
+
+        setTimeout(() => {
+          expect(mockAxios).not.toHaveBeenCalled()
+          done()
+        })
+      })
+
+      it('check api is called and param.opts when listUuidOpts() is called with currentAction.mapping[param.name].api', (done) => {
+        const param = { name: 'template', type: 'uuid' }
+        const mockData = {
+          testapinamecase1response: {
+            count: 1,
+            testapinamecase1: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          }
+        }
+
+        wrapper = factory({
+          data: {
+            currentAction: {
+              mapping: {
+                template: {
+                  api: 'testApiNameCase1'
+                }
+              }
+            }
+          }
+        })
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper.vm.listUuidOpts(param)
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              listall: true,
+              response: 'json'
+            }
+          })
+          expect(param).toEqual({
+            name: 'template',
+            type: 'uuid',
+            loading: false,
+            opts: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when listUuidOpts() is called with store apis has api startWith param.name', (done) => {
+        const param = { name: 'testapiname', type: 'uuid' }
+        const mockData = {
+          listtestapinamesresponse: {
+            count: 1,
+            testapiname: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          }
+        }
+
+        wrapper = factory()
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper.vm.listUuidOpts(param)
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'listTestApiNames',
+              listall: true,
+              response: 'json'
+            }
+          })
+          expect(param).toEqual({
+            name: 'testapiname',
+            type: 'uuid',
+            loading: false,
+            opts: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called with params has item name and value assign by resource', (done) => {
+        const param = { name: 'template', type: 'uuid' }
+        const mockData = {
+          testapinamecase1response: {
+            count: 0,
+            testapinamecase1: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          }
+        }
+
+        wrapper = factory({
+          data: {
+            currentAction: {
+              mapping: {
+                template: {
+                  api: 'testApiNameCase1',
+                  params: (record) => {
+                    return {
+                      name: record.name
+                    }
+                  }
+                }
+              }
+            }
+          }
+        })
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper.setData({
+          resource: {
+            id: 'test-id-value',
+            name: 'test-name-value'
+          }
+        })
+        wrapper.vm.listUuidOpts(param)
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              listall: true,
+              name: 'test-name-value',
+              response: 'json'
+            }
+          })
+          expect(param).toEqual({
+            name: 'template',
+            type: 'uuid',
+            loading: false,
+            opts: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called with params has item templatefilter when apiName is listTemplates', (done) => {
+        const param = { name: 'id', type: 'uuid' }
+        const mockData = {
+          listtemplateresponse: {
+            count: 1,
+            templates: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          }
+        }
+
+        wrapper = factory()
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper.setData({
+          apiName: 'listTemplates'
+        })
+        wrapper.vm.listUuidOpts(param)
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'listTemplates',
+              listall: true,
+              templatefilter: 'executable',
+              response: 'json'
+            }
+          })
+          expect(param).toEqual({
+            name: 'id',
+            type: 'uuid',
+            loading: false,
+            opts: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called with params has item isofilter when apiName is listIsos', (done) => {
+        const param = { name: 'id', type: 'uuid' }
+        const mockData = {
+          listisosresponse: {
+            count: 1,
+            iso: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          }
+        }
+
+        wrapper = factory()
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper.setData({
+          apiName: 'listIsos'
+        })
+        wrapper.vm.listUuidOpts(param)
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'listIsos',
+              listall: true,
+              isofilter: 'executable',
+              response: 'json'
+            }
+          })
+          expect(param).toEqual({
+            name: 'id',
+            type: 'uuid',
+            loading: false,
+            opts: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called with params has item type = routing when apiName is listHosts', (done) => {
+        const param = { name: 'id', type: 'uuid' }
+        const mockData = {
+          listhostresponse: {
+            count: 1,
+            hosts: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          }
+        }
+
+        wrapper = factory()
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper.setData({
+          apiName: 'listHosts'
+        })
+        wrapper.vm.listUuidOpts(param)
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'listHosts',
+              listall: true,
+              type: 'routing',
+              response: 'json'
+            }
+          })
+          expect(param).toEqual({
+            name: 'id',
+            type: 'uuid',
+            loading: false,
+            opts: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called and param.opts is empty when api throw error', (done) => {
+        const param = { name: 'id', type: 'uuid', loading: true }
+        const errorMock = {
+          response: {},
+          stack: 'Error: throw exception error'
+        }
+
+        wrapper = factory()
+
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+        mockAxios.mockRejectedValue(errorMock)
+        wrapper.setData({
+          apiName: 'testApiNameCase1'
+        })
+        wrapper.vm.listUuidOpts(param)
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              listall: true,
+              response: 'json'
+            }
+          })
+          expect(param).toEqual({
+            name: 'id',
+            type: 'uuid',
+            loading: false,
+            opts: []
+          })
+
+          done()
+        })
+      })
+    })
+
+    describe('pollActionCompletion()', () => {
+      it('check $notification, fetchData() when pollActionCompletion() is called with action is empty', (done) => {
+        const mockData = {
+          queryasyncjobresultresponse: {
+            jobstatus: 1,
+            jobresult: {
+              name: 'test-name-value'
+            }
+          }
+        }
+
+        wrapper = factory()
+
+        const jobId = 'test-job-id'
+        const action = {}
+        const spy = jest.spyOn(wrapper.vm, 'fetchData')
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper.vm.pollActionCompletion(jobId, action)
+
+        setTimeout(() => {
+          expect(spy).toHaveBeenCalled()
+          expect(mocks.$notification.info).not.toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'queryAsyncJobResult',
+              jobId: jobId,
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check $notification, fetchData() when pollActionCompletion() is called with action is not empty', (done) => {
+        const mockData = {
+          queryasyncjobresultresponse: {
+            jobstatus: 1,
+            jobresult: {
+              name: 'test-name-value'
+            }
+          }
+        }
+
+        wrapper = factory()
+
+        const jobId = 'test-job-id'
+        const action = {
+          label: 'labelname',
+          response: (jobResult) => {
+            return jobResult.name
+          }
+        }
+        const spy = jest.spyOn(wrapper.vm, 'fetchData')
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper.vm.pollActionCompletion(jobId, action)
+
+        setTimeout(() => {
+          expect(spy).toHaveBeenCalled()
+          expect(mocks.$notification.info).toHaveBeenCalled()
+          expect(mocks.$notification.info).toHaveLastReturnedWith({
+            message: 'test-name-en',
+            description: 'test-description',
+            duration: 0
+          })
+          expect(mockAxios).toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'queryAsyncJobResult',
+              jobId: jobId,
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check fetchData() is called when $pollJob error response', (done) => {
+        const mockData = {
+          queryasyncjobresultresponse: {
+            jobstatus: 2,
+            jobresult: {
+              errortext: 'test-error-message'
+            }
+          }
+        }
+
+        wrapper = factory()
+
+        const jobId = 'test-job-id'
+        const action = {
+          label: 'labelname',
+          response: (jobResult) => {
+            return jobResult.name
+          }
+        }
+        const spy = jest.spyOn(wrapper.vm, 'fetchData')
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper.vm.pollActionCompletion(jobId, action)
+
+        setTimeout(() => {
+          expect(spy).toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'queryAsyncJobResult',
+              jobId: jobId,
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+    })
+
+    describe('fillEditFormFieldValues()', () => {
+      it('check form getFieldDecorator() is called and formModel when currentAction.paramFields has item type = list', (done) => {
+        wrapper = factory({
+          data: {
+            currentAction: {
+              paramFields: [
+                { name: 'domainids', type: 'list' }
+              ],
+              mapping: {
+                column1: () => { return 'test-column' }
+              }
+            },
+            resource: {
+              domainname: ['test-domain-value-1', 'test-domain-value-2']
+            }
+          }
+        })
+
+        const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator')
+
+        wrapper.vm.fillEditFormFieldValues()
+
+        wrapper.vm.$nextTick(() => {
+          expect(spy).toHaveBeenCalled()
+          expect(spy).toBeCalledWith('domainids', {
+            initialValue: ['test-domain-value-1', 'test-domain-value-2']
+          })
+          expect(wrapper.vm.formModel).toEqual({ domainids: ['test-domain-value-1', 'test-domain-value-2'] })
+
+          done()
+        })
+      })
+
+      it('check form getFieldDecorator() is called and formModel when currentAction.paramFields has item name = account', (done) => {
+        wrapper = factory({
+          data: {
+            currentAction: {
+              paramFields: [
+                { name: 'account', type: 'string' }
+              ],
+              mapping: {
+                column1: () => { return 'test-column' }
+              }
+            },
+            resource: {
+              account: 'test-account-value'
+            }
+          }
+        })
+
+        const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator')
+
+        wrapper.vm.fillEditFormFieldValues()
+
+        wrapper.vm.$nextTick(() => {
+          expect(spy).toHaveBeenCalled()
+          expect(spy).toBeCalledWith('account', {
+            initialValue: 'test-account-value'
+          })
+          expect(wrapper.vm.formModel).toEqual({ account: 'test-account-value' })
+
+          done()
+        })
+      })
+
+      it('check form getFieldDecorator() is called and formModel when currentAction.paramFields has item exists in currentAction. mapping', (done) => {
+        wrapper = factory({
+          data: {
+            currentAction: {
+              paramFields: [
+                { name: 'column1', type: 'string' }
+              ],
+              mapping: {
+                column1: () => { return 'test-column' }
+              }
+            },
+            resource: {
+              column1: 'test-column-value'
+            }
+          }
+        })
+
+        const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator')
+
+        wrapper.vm.fillEditFormFieldValues()
+
+        wrapper.vm.$nextTick(() => {
+          expect(spy).toHaveBeenCalled()
+          expect(spy).toBeCalledWith('column1', {
+            initialValue: 'test-column-value'
+          })
+          expect(wrapper.vm.formModel).toEqual({ column1: 'test-column-value' })
+
+          done()
+        })
+      })
+
+      it('check form getFieldDecorator() is called and formModel when currentAction.paramFields has item exists in resource', (done) => {
+        wrapper = factory({
+          data: {
+            currentAction: {
+              paramFields: [
+                { name: 'column1', type: 'string' }
+              ]
+            },
+            resource: {
+              column1: 'test-column-value'
+            }
+          }
+        })
+
+        const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator')
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.fillEditFormFieldValues()
+
+          expect(spy).toHaveBeenCalled()
+          expect(spy).toBeCalledWith('column1', {
+            initialValue: 'test-column-value'
+          })
+          expect(wrapper.vm.formModel).toEqual({ column1: 'test-column-value' })
+
+          done()
+        })
+      })
+
+      it('check form getFieldDecorator() is called and formModel when currentAction.paramFields have not item in resource', (done) => {
+        wrapper = factory({
+          data: {
+            currentAction: {
+              paramFields: [
+                { name: 'column1', type: 'string' }
+              ]
+            },
+            resource: {}
+          }
+        })
+
+        const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator')
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.fillEditFormFieldValues()
+
+          expect(spy).not.toHaveBeenCalled()
+          expect(wrapper.vm.formModel).toEqual({})
+
+          done()
+        })
+      })
+
+      it('check form getFieldDecorator() is not called when field value is null', (done) => {
+        wrapper = factory({
+          data: {
+            currentAction: {
+              paramFields: [
+                { name: 'column1', type: 'string' }
+              ]
+            },
+            resource: {
+              column1: null
+            }
+          }
+        })
+
+        const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator')
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.fillEditFormFieldValues()
+
+          expect(spy).not.toHaveBeenCalled()
+
+          done()
+        })
+      })
+    })
+
+    describe('changeFilter()', () => {
+      it('check `route.query` when changeFilter() is called with filter', async () => {
+        wrapper = factory()
+
+        await wrapper.vm.$nextTick()
+        wrapper.vm.changeFilter('test')
+
+        expect(router.currentRoute.query).toEqual({
+          filter: 'test',
+          page: 1,
+          pagesize: 20
+        })
+      })
+
+      it('check `route.query` when changeFilter() is called with `$route.name` equal `template`', async () => {
+        router = common.createMockRouter([{
+          name: 'template',
+          path: '/test-router-1',
+          meta: {
+            icon: 'test'
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'template' })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.changeFilter('test')
+
+        expect(router.currentRoute.query).toEqual({
+          templatefilter: 'test',
+          filter: 'test',
+          page: 1,
+          pagesize: 20
+        })
+      })
+
+      it('check `route.query` when changeFilter() is called with `$route.name` equal `iso`', async () => {
+        router = common.createMockRouter([{
+          name: 'iso',
+          path: '/test-router-1',
+          meta: {
+            icon: 'test'
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'iso' })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.changeFilter('test')
+
+        expect(router.currentRoute.query).toEqual({
+          isofilter: 'test',
+          filter: 'test',
+          page: 1,
+          pagesize: 20
+        })
+      })
+
+      it('check `route.query` when changeFilter() is called with `$route.name` equal `vm` and `filter` equal `self`', async () => {
+        router = common.createMockRouter([{
+          name: 'vm',
+          path: '/test-router-1',
+          meta: {
+            icon: 'test'
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'vm' })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.changeFilter('self')
+
+        expect(router.currentRoute.query).toEqual({
+          account: 'test-account',
+          domainid: 'test-domain-id',
+          filter: 'self',
+          page: 1,
+          pagesize: 20
+        })
+      })
+
+      it('check `route.query` when changeFilter() is called with `$route.name` equal `vm` and `filter` equal `running`', async () => {
+        router = common.createMockRouter([{
+          name: 'vm',
+          path: '/test-router-1',
+          meta: {
+            icon: 'test'
+          }
+        }])
+        wrapper = factory({ router: router })
+
+        router.push({ name: 'vm' })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.changeFilter('running')
+
+        expect(router.currentRoute.query).toEqual({
+          state: 'running',
+          filter: 'running',
+          page: 1,
+          pagesize: 20
+        })
+      })
+    })
+
+    describe('changePage()', () => {
+      it('check page, pageSize when changePage() is called', () => {
+        wrapper = factory()
+
+        wrapper.vm.$nextTick(() => {
+          expect(router.currentRoute.query).toEqual({})
+          wrapper.vm.changePage(1, 10)
+          expect(router.currentRoute.query).toEqual({
+            page: 1,
+            pagesize: 10
+          })
+        })
+      })
+    })
+
+    describe('changePageSize()', () => {
+      it('check page, pageSize and fetchData() when changePageSize() is called', () => {
+        wrapper = factory()
+
+        wrapper.vm.$nextTick(() => {
+          expect(router.currentRoute.query).toEqual({
+            page: 1,
+            pagesize: 10
+          })
+          wrapper.vm.changePageSize(2, 20)
+          expect(router.currentRoute.query).toEqual({
+            page: 2,
+            pagesize: 20
+          })
+        })
+      })
+    })
+
+    describe('start()', () => {
+      it('check loading, selectedRowKeys, fetchData() when start() is called', async (done) => {
+        wrapper = factory()
+
+        const spy = jest.spyOn(wrapper.vm, 'fetchData')
+        await wrapper.vm.$nextTick()
+        await wrapper.vm.start()
+
+        expect(spy).toBeCalled()
+
+        setTimeout(() => {
+          expect(wrapper.vm.loading).toBeFalsy()
+          expect(wrapper.vm.selectedRowKeys).toEqual([])
+
+          done()
+        }, 1000)
+      })
+    })
+
+    describe('toggleLoading()', () => {
+      it('check loading when toggleLoading() is called', () => {
+        wrapper = factory({
+          data: {
+            loading: false
+          }
+        })
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.loading).toBeFalsy()
+
+          wrapper.vm.toggleLoading()
+
+          expect(wrapper.vm.loading).toBeTruthy()
+        })
+      })
+    })
+
+    describe('startLoading()', () => {
+      it('check loading when startLoading() is called', () => {
+        wrapper = factory({
+          data: {
+            loading: false
+          }
+        })
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.loading).toBeFalsy()
+
+          wrapper.vm.startLoading()
+
+          expect(wrapper.vm.loading).toBeTruthy()
+        })
+      })
+    })
+
+    describe('finishLoading()', () => {
+      it('check loading when finishLoading() is called', () => {
+        wrapper = factory({
+          data: {
+            loading: true
+          }
+        })
+
+        wrapper.vm.$nextTick(() => {
+          expect(wrapper.vm.loading).toBeTruthy()
+
+          wrapper.vm.finishLoading()
+
+          expect(wrapper.vm.loading).toBeFalsy()
+        })
+      })
+    })
+
+    describe('execSubmit()', () => {
+      it('check error from validateFields', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              loading: false,
+              label: 'labelname',
+              params: [
+                { name: 'id', type: 'uuid' }
+              ],
+              paramFields: [
+                { name: 'id', type: 'uuid', description: '', required: true }
+              ],
+              mapping: {}
+            },
+            resource: {
+              id: 'test-id-value'
+            }
+          }
+        })
+
+        spyConsole.warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).not.toBeCalled()
+          done()
+        })
+      })
+
+      it('check api is called with params has item id equal resource.id', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                { name: 'id', type: 'uuid' }
+              ],
+              paramFields: [
+                { name: 'id', type: 'uuid', description: '', required: false }
+              ],
+              mapping: {}
+            },
+            resource: {
+              id: 'test-id-value'
+            }
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              id: 'test-id-value',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when form has input key not exist in currentAction.params', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                { name: 'id', type: 'uuid' }
+              ],
+              paramFields: [
+                { name: 'name', type: 'string', description: '', required: false },
+                { name: 'id', type: 'uuid', description: '', required: false }
+              ],
+              mapping: {}
+            },
+            resource: {
+              id: 'test-id-value'
+            }
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              id: 'test-id-value',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when form has input key exist in currentAction.params, type is boolean and value is undefined', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                { name: 'column1', type: 'boolean' }
+              ],
+              paramFields: [
+                { name: 'column1', type: 'boolean', description: '', required: false }
+              ],
+              mapping: {}
+            },
+            resource: {}
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              column1: false,
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when form has input key exist in currentAction.params, type is boolean and value is null', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                { name: 'column1', type: 'boolean' }
+              ],
+              paramFields: [
+                { name: 'column1', type: 'boolean', description: '', required: false }
+              ],
+              mapping: {}
+            },
+            resource: {}
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.form.getFieldDecorator('column1', { initialValue: null })
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              column1: false,
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when form has input key exist in currentAction.mapping', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                { name: 'column1', type: 'list' }
+              ],
+              paramFields: [
+                { name: 'column1', type: 'list', description: '', required: false }
+              ],
+              mapping: {
+                column1: {
+                  options: ['column-value1', 'column-value2']
+                }
+              }
+            },
+            resource: {}
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.form.getFieldDecorator('column1', { initialValue: 1 })
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              column1: 'column-value2',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when form has input key not exist in currentAction.mapping, type is list and currentAction.params[input] has id', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                {
+                  name: 'column1',
+                  type: 'list',
+                  opts: [
+                    { id: 'test-id-1', value: 'test-value-1' },
+                    { id: 'test-id-2', value: 'test-value-2' },
+                    { id: 'test-id-3', value: 'test-value-3' }
+                  ]
+                }
+              ],
+              paramFields: [
+                { name: 'column1', type: 'list', description: '', required: false }
+              ],
+              mapping: {
+              }
+            },
+            resource: {}
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.form.getFieldDecorator('column1', { initialValue: [1, 2] })
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              column1: 'test-id-2,test-id-3',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when form has input key has name = account, currentAction.api = createAccount', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'createAccount',
+              loading: false,
+              label: 'labelname',
+              params: [
+                {
+                  name: 'account',
+                  type: 'string'
+                }
+              ],
+              paramFields: [
+                { name: 'account', type: 'string', description: '', required: false }
+              ],
+              mapping: {}
+            },
+            resource: {}
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.form.getFieldDecorator('account', { initialValue: 'test-account-value' })
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'createAccount',
+              account: 'test-account-value',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when form has input key has name = keypair, currentAction.api = addAccountToProject', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'addAccountToProject',
+              loading: false,
+              label: 'labelname',
+              params: [
+                {
+                  name: 'keypair',
+                  type: 'string'
+                }
+              ],
+              paramFields: [
+                { name: 'keypair', type: 'string', description: '', required: false }
+              ],
+              mapping: {}
+            },
+            resource: {}
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.form.getFieldDecorator('keypair', { initialValue: 'test-keypair-value' })
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'addAccountToProject',
+              keypair: 'test-keypair-value',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when form has input key name = (account | keypair), currentAction.api != (addAccountToProject | createAccount)', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                {
+                  name: 'keypair',
+                  type: 'string',
+                  opts: [
+                    { id: 'test-id-1', name: 'test-name-1' },
+                    { id: 'test-id-2', name: 'test-name-2' }
+                  ]
+                }
+              ],
+              paramFields: [
+                { name: 'keypair', type: 'string', description: '', required: false }
+              ],
+              mapping: {}
+            },
+            resource: {}
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.form.getFieldDecorator('keypair', { initialValue: 1 })
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              keypair: 'test-name-2',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when form has input key do not fall under special condition.', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                {
+                  name: 'column1',
+                  type: 'string'
+                }
+              ],
+              paramFields: [
+                { name: 'column1', type: 'string', description: '', required: false }
+              ],
+              mapping: {}
+            },
+            resource: {}
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column-value' })
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              column1: 'test-column-value',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when currentAction has defaultArgs', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                { name: 'column1', type: 'string' }
+              ],
+              paramFields: [
+                { name: 'column1', type: 'string', description: '', required: false }
+              ],
+              mapping: {},
+              defaultArgs: {
+                column2: 'test-column2-value'
+              }
+            },
+            resource: {}
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column1-value' })
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              column1: 'test-column1-value',
+              column2: 'test-column2-value',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when currentAction.mapping has value and value is function', (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                { name: 'column1', type: 'string' }
+              ],
+              paramFields: [
+                { name: 'column1', type: 'string', description: '', required: false }
+              ],
+              mapping: {
+                column2: {
+                  value: (record, params) => {
+                    return record.name
+                  }
+                }
+              }
+            },
+            resource: {
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            testapinamecase1: {}
+          }
+        }
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column1-value' })
+          const event = document.createEvent('Event')
+          wrapper.vm.execSubmit(event)
+
+          expect(mockAxios).toHaveBeenCalledTimes(1)
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'testApiNameCase1',
+              column1: 'test-column1-value',
+              column2: 'test-name-value',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check router name when api is called and currentAction.icon = delete and dataView is true', async (done) => {
+        router = common.createMockRouter([{
+          name: 'testRouter26',
+          path: '/test-router-26',
+          meta: {
+            icon: 'test-router-26'
+          }
+        }])
+        wrapper = factory({ router: router })
+        router.push({ name: 'testRouter26' })
+
+        const mockData = {
+          testapinamecase1response: {
+            count: 1,
+            testapinamecase1: [{
+              id: 'test-id-value',
+              name: 'test-name-value'
+            }]
+          }
+        }
+
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+        await wrapper.vm.$nextTick()
+
+        expect(router.currentRoute.name).toEqual('testRouter26')
+
+        wrapper.setData({
+          currentAction: {
+            icon: 'delete',
+            api: 'testApiNameCase1',
+            loading: false,
+            label: 'labelname',
+            params: [
+              { name: 'column1', type: 'string' }
+            ],
+            paramFields: [
+              { name: 'column1', type: 'string', description: '', required: false }
+            ]
+          },
+          dataView: true
+        })
+
+        wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column1-value' })
+        const event = document.createEvent('Event')
+        await wrapper.vm.execSubmit(event)
+
+        setTimeout(() => {
+          expect(router.currentRoute.name).toEqual('home')
+          done()
+        }, 1000)
+      })
+
+      it('check pollActionCompletion() and action AddAsyncJob is called when api is called and response have jobId result', async (done) => {
+        store = common.createMockStore(state, actions)
+        wrapper = factory({
+          store: store,
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                { name: 'column1', type: 'string' }
+              ],
+              paramFields: [
+                { name: 'column1', type: 'string', description: '', required: false }
+              ]
+            },
+            resource: {}
+          }
+        })
+
+        const spyPollAction = jest.spyOn(wrapper.vm, 'pollActionCompletion').mockImplementation(() => {})
+        const mockData = {
+          testapinamecase1response: {
+            jobid: 'test-job-id'
+          }
+        }
+
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        await wrapper.vm.$nextTick()
+        wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column1-value' })
+        const event = document.createEvent('Event')
+        wrapper.vm.execSubmit(event)
+
+        setTimeout(() => {
+          expect(actions.AddAsyncJob).toHaveBeenCalled()
+          expect(spyPollAction).toHaveBeenCalled()
+
+          done()
+        })
+      })
+
+      it('check $notification when api is called and response have not jobId result', async (done) => {
+        wrapper = factory({
+          data: {
+            showAction: true,
+            currentAction: {
+              api: 'testApiNameCase1',
+              loading: false,
+              label: 'labelname',
+              params: [
+                { name: 'column1', type: 'string' }
+              ],
+              paramFields: [
+                { name: 'column1', type: 'string', description: '', required: false }
+              ]
+            },
+            resource: {
+              name: 'test-name-value'
+            }
+          }
+        })
+
+        const mockData = {
+          testapinamecase1response: {
+            count: 1,
+            testapinamecase1: [{
+              id: 'test-id-value'
+            }]
+          }
+        }
+
+        mockAxios.mockResolvedValue(mockData)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        await wrapper.vm.$nextTick()
+        wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column1-value' })
+        const event = document.createEvent('Event')
+        wrapper.vm.execSubmit(event)
+
+        setTimeout(() => {
+          expect(mocks.$message.success).toHaveBeenCalled()
+          expect(mocks.$message.success).toHaveLastReturnedWith({
+            content: 'test-name-en - test-name-value',
+            key: 'labelnametest-name-value',
+            duration: 2
+          })
+
+          done()
+        })
+      })
+
+      it('check $notifyError is called when api is called with throw error', async (done) => {
+        wrapper = factory()
+
+        const errorMock = {
+          response: {},
+          message: 'Error: throw exception error'
+        }
+        mockAxios.mockRejectedValue(errorMock)
+        spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {})
+
+        await wrapper.vm.$nextTick()
+        const event = document.createEvent('Event')
+        await wrapper.vm.execSubmit(event)
+
+        setTimeout(() => {
+          expect(mocks.$notifyError).toHaveBeenCalledTimes(1)
+          expect(mocks.$notifyError).toHaveBeenCalledWith(errorMock)
+
+          done()
+        })
+      })
+    })
+  })
+})
diff --git a/tests/unit/views/compute/MigrateWizard.spec.js b/tests/unit/views/compute/MigrateWizard.spec.js
new file mode 100644
index 0000000..14c8a89
--- /dev/null
+++ b/tests/unit/views/compute/MigrateWizard.spec.js
@@ -0,0 +1,686 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import mockAxios from '../../../mock/mockAxios'
+import MigrateWizard from '@/views/compute/MigrateWizard'
+import common from '../../../common'
+import mockData from '../../../mockData/MigrateWizard.mock'
+
+jest.mock('axios', () => mockAxios)
+
+let wrapper, i18n, store, mocks
+
+const state = {}
+const actions = {
+  AddAsyncJob: jest.fn((jobObject) => {})
+}
+mocks = {
+  $message: {
+    error: jest.fn((message) => {})
+  },
+  $notification: {
+    error: jest.fn((message) => {})
+  },
+  $pollJob: jest.fn((obj) => {
+    switch (obj.jobId) {
+      case 'test-job-id-case-1':
+        if ('successMethod' in obj) {
+          obj.successMethod()
+        }
+        break
+      case 'test-job-id-case-2':
+        if ('errorMethod' in obj) {
+          obj.errorMethod()
+        }
+        break
+      case 'test-job-id-case-3':
+        if ('catchMethod' in obj) {
+          obj.catchMethod()
+        }
+        break
+    }
+  })
+}
+i18n = common.createMockI18n('en', mockData.messages)
+store = common.createMockStore(state, actions)
+
+const factory = (opts = {}) => {
+  i18n = opts.i18n || i18n
+  store = opts.store || store
+  mocks = opts.mocks || mocks
+
+  return common.createFactory(MigrateWizard, {
+    i18n,
+    store,
+    mocks,
+    props: opts.props || {},
+    data: opts.data || {}
+  })
+}
+
+describe('Views > compute > MigrateWizard.vue', () => {
+  jest.spyOn(console, 'warn').mockImplementation(() => {})
+
+  beforeEach(() => {
+    jest.clearAllMocks()
+
+    if (wrapper) {
+      wrapper.destroy()
+    }
+
+    if (i18n.locale !== 'en') {
+      i18n.locale = 'en'
+    }
+  })
+
+  describe('Methods', () => {
+    describe('fetchData()', () => {
+      it('check api is called with resource is empty and searchQuery is null', () => {
+        const mockData = {
+          findhostsformigrationresponse: {
+            count: 0,
+            host: []
+          }
+        }
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper = factory({
+          props: {
+            resource: {}
+          }
+        })
+
+        wrapper.vm.$nextTick(() => {
+          expect(mockAxios).toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'findHostsForMigration',
+              virtualmachineid: undefined,
+              keyword: '',
+              page: 1,
+              pagesize: 10,
+              response: 'json'
+            }
+          })
+        })
+      })
+
+      it('check api is called with resource.id is null and searchQuery is null', () => {
+        const mockData = {
+          findhostsformigrationresponse: {
+            count: 0,
+            host: []
+          }
+        }
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper = factory({
+          props: {
+            resource: { id: null }
+          }
+        })
+
+        wrapper.vm.$nextTick(() => {
+          expect(mockAxios).toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'findHostsForMigration',
+              virtualmachineid: null,
+              keyword: '',
+              page: 1,
+              pagesize: 10,
+              response: 'json'
+            }
+          })
+        })
+      })
+
+      it('check api is called with resource.id is not null and searchQuery is null', () => {
+        const mockData = {
+          findhostsformigrationresponse: {
+            count: 0,
+            host: []
+          }
+        }
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper = factory({
+          props: {
+            resource: { id: 'test-id-value' }
+          }
+        })
+
+        wrapper.vm.$nextTick(() => {
+          expect(mockAxios).toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'findHostsForMigration',
+              virtualmachineid: 'test-id-value',
+              keyword: '',
+              page: 1,
+              pagesize: 10,
+              response: 'json'
+            }
+          })
+        })
+      })
+
+      it('check api is called with resource.id is not null and searchQuery is not null', () => {
+        const mockData = {
+          findhostsformigrationresponse: {
+            count: 0,
+            host: []
+          }
+        }
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper = factory({
+          props: { resource: { id: 'test-id-value' } },
+          data: { searchQuery: 'test-query-value' }
+        })
+
+        wrapper.vm.$nextTick(() => {
+          expect(mockAxios).toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'findHostsForMigration',
+              virtualmachineid: 'test-id-value',
+              keyword: 'test-query-value',
+              page: 1,
+              pagesize: 10,
+              response: 'json'
+            }
+          })
+        })
+      })
+
+      it('check api is called with params assign by resource, searchQuery, page, pageSize', () => {
+        const mockData = {
+          findhostsformigrationresponse: {
+            count: 0,
+            host: []
+          }
+        }
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper = factory({
+          props: { resource: { id: 'test-id-value' } },
+          data: {
+            searchQuery: 'test-query-value',
+            page: 2,
+            pageSize: 20
+          }
+        })
+
+        wrapper.vm.$nextTick(() => {
+          expect(mockAxios).toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'findHostsForMigration',
+              virtualmachineid: 'test-id-value',
+              keyword: 'test-query-value',
+              page: 2,
+              pagesize: 20,
+              response: 'json'
+            }
+          })
+        })
+      })
+
+      it('check hosts, totalCount when api is called with response result is empty', async (done) => {
+        const mockData = {
+          findhostsformigrationresponse: {
+            count: 0,
+            host: []
+          }
+        }
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper = factory({ props: { resource: {} } })
+
+        await wrapper.vm.$nextTick()
+
+        setTimeout(() => {
+          expect(wrapper.vm.hosts).toEqual([])
+          expect(wrapper.vm.totalCount).toEqual(0)
+
+          done()
+        })
+      })
+
+      it('check hosts, totalCount when api is called with response result is not empty', async (done) => {
+        const mockData = {
+          findhostsformigrationresponse: {
+            count: 1,
+            host: [{
+              id: 'test-host-id',
+              name: 'test-host-name',
+              suitability: 'test-host-suitability',
+              cpuused: 'test-host-cpuused',
+              memused: 'test-host-memused',
+              select: 'test-host-select'
+            }]
+          }
+        }
+
+        mockAxios.mockResolvedValue(mockData)
+        wrapper = factory({ props: { resource: {} } })
+
+        await wrapper.vm.$nextTick()
+
+        setTimeout(() => {
+          expect(wrapper.vm.hosts).toEqual([{
+            id: 'test-host-id',
+            name: 'test-host-name',
+            suitability: 'test-host-suitability',
+            cpuused: 'test-host-cpuused',
+            memused: 'test-host-memused',
+            select: 'test-host-select'
+          }])
+          expect(wrapper.vm.totalCount).toEqual(1)
+
+          done()
+        })
+      })
+
+      it('check $message.error is called when api is called with throw error', async (done) => {
+        const mockError = 'Error: throw error message'
+        console.error = jest.fn()
+
+        mockAxios.mockRejectedValue(mockError)
+        wrapper = factory({ props: { resource: {} } })
+
+        await wrapper.vm.$nextTick()
+
+        setTimeout(() => {
+          expect(mocks.$message.error).toHaveBeenCalled()
+          expect(mocks.$message.error).toHaveBeenCalledWith(`${i18n.t('message.load.host.failed')}: ${mockError}`)
+
+          done()
+        })
+      })
+    })
+
+    describe('submitForm()', () => {
+      it('check api is called when selectedHost.requiresStorageMotion is true', async (done) => {
+        const mockData = {
+          migratevirtualmachineresponse: {
+            jobid: 'test-job-id'
+          },
+          queryasyncjobresultresponse: {
+            jobstatus: 1,
+            jobresult: {
+              name: 'test-name-value'
+            }
+          }
+        }
+
+        wrapper = factory({
+          props: {
+            resource: {
+              id: 'test-resource-id',
+              name: 'test-resource-name'
+            }
+          },
+          data: {
+            selectedHost: {
+              requiresStorageMotion: true,
+              id: 'test-host-id',
+              name: 'test-host-name'
+            }
+          }
+        })
+        jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
+        mockAxios.mockResolvedValue(mockData)
+        await wrapper.vm.$nextTick()
+        await wrapper.vm.submitForm()
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'migrateVirtualMachineWithVolume',
+              hostid: 'test-host-id',
+              virtualmachineid: 'test-resource-id',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check api is called when selectedHost.requiresStorageMotion is false', async (done) => {
+        const mockData = {
+          migratevirtualmachineresponse: {
+            jobid: 'test-job-id'
+          },
+          queryasyncjobresultresponse: {
+            jobstatus: 1,
+            jobresult: {
+              name: 'test-name-value'
+            }
+          }
+        }
+        wrapper = factory({
+          props: {
+            resource: {
+              id: 'test-resource-id',
+              name: 'test-resource-name'
+            }
+          },
+          data: {
+            selectedHost: {
+              requiresStorageMotion: false,
+              id: 'test-host-id',
+              name: 'test-host-name'
+            }
+          }
+        })
+        jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
+
+        mockAxios.mockResolvedValue(mockData)
+
+        await wrapper.vm.$nextTick()
+        await wrapper.vm.submitForm()
+
+        setTimeout(() => {
+          expect(mockAxios).toHaveBeenCalled()
+          expect(mockAxios).toHaveBeenCalledWith({
+            url: '/',
+            method: 'GET',
+            data: new URLSearchParams(),
+            params: {
+              command: 'migrateVirtualMachine',
+              hostid: 'test-host-id',
+              virtualmachineid: 'test-resource-id',
+              response: 'json'
+            }
+          })
+
+          done()
+        })
+      })
+
+      it('check store dispatch `AddAsyncJob` and $pollJob have successMethod() is called with requiresStorageMotion is true', async (done) => {
+        const mockData = {
+          migratevirtualmachinewithvolumeresponse: {
+            jobid: 'test-job-id-case-1'
+          },
+          queryasyncjobresultresponse: {
+            jobstatus: 1,
+            jobresult: {
+              name: 'test-name-value'
+            }
+          }
+        }
+        wrapper = factory({
+          props: {
+            resource: {
+              id: 'test-resource-id',
+              name: 'test-resource-name'
+            }
+          },
+          data: {
+            selectedHost: {
+              requiresStorageMotion: true,
+              id: 'test-host-id',
+              name: 'test-host-name'
+            }
+          }
+        })
+        jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
+
+        mockAxios.mockResolvedValue(mockData)
+
+        await wrapper.vm.$nextTick()
+        await wrapper.vm.submitForm()
+
+        setTimeout(() => {
+          expect(actions.AddAsyncJob).toHaveBeenCalled()
+          expect(mocks.$pollJob).toHaveBeenCalled()
+          expect(wrapper.emitted()['close-action'][0]).toEqual([])
+
+          done()
+        })
+      })
+
+      it('check store dispatch `AddAsyncJob` and $pollJob have successMethod() is called with requiresStorageMotion is false', async (done) => {
+        const mockData = {
+          migratevirtualmachineresponse: {
+            jobid: 'test-job-id-case-2'
+          },
+          queryasyncjobresultresponse: {
+            jobstatus: 1,
+            jobresult: {
+              name: 'test-name-value'
+            }
+          }
+        }
+        wrapper = factory({
+          props: {
+            resource: {
+              id: 'test-resource-id',
+              name: 'test-resource-name'
+            }
+          },
+          data: {
+            selectedHost: {
+              requiresStorageMotion: false,
+              id: 'test-host-id',
+              name: 'test-host-name'
+            }
+          }
+        })
+        jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
+
+        mockAxios.mockResolvedValue(mockData)
+
+        await wrapper.vm.$nextTick()
+        await wrapper.vm.submitForm()
+
+        setTimeout(() => {
+          expect(actions.AddAsyncJob).toHaveBeenCalled()
+          expect(mocks.$pollJob).toHaveBeenCalled()
+          expect(wrapper.emitted()['close-action'][0]).toEqual([])
+
+          done()
+        })
+      })
+
+      it('check store dispatch `AddAsyncJob` and $pollJob have errorMethod() is called', async (done) => {
+        const mockData = {
+          migratevirtualmachinewithvolumeresponse: {
+            jobid: 'test-job-id-case-3'
+          },
+          queryasyncjobresultresponse: {
+            jobstatus: 2,
+            jobresult: {
+              errortext: 'test-error-message'
+            }
+          }
+        }
+        wrapper = factory({
+          props: {
+            resource: {
+              id: 'test-resource-id',
+              name: 'test-resource-name'
+            }
+          },
+          data: {
+            selectedHost: {
+              requiresStorageMotion: true,
+              id: 'test-host-id',
+              name: 'test-host-name'
+            }
+          }
+        })
+        jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
+
+        mockAxios.mockResolvedValue(mockData)
+
+        await wrapper.vm.$nextTick()
+        await wrapper.vm.submitForm()
+
+        setTimeout(() => {
+          expect(actions.AddAsyncJob).toHaveBeenCalled()
+          expect(mocks.$pollJob).toHaveBeenCalled()
+          expect(wrapper.emitted()['close-action'][0]).toEqual([])
+
+          done()
+        })
+      })
+
+      it('check store dispatch `AddAsyncJob` and $pollJob have catchMethod() is called', async (done) => {
+        const mockData = {
+          migratevirtualmachinewithvolumeresponse: {
+            jobid: 'test-job-id-case-4'
+          }
+        }
+        wrapper = factory({
+          props: {
+            resource: {
+              id: 'test-resource-id',
+              name: 'test-resource-name'
+            }
+          },
+          data: {
+            selectedHost: {
+              requiresStorageMotion: true,
+              id: 'test-host-id',
+              name: 'test-host-name'
+            }
+          }
+        })
+        jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
+
+        mockAxios.mockResolvedValue(mockData)
+
+        await wrapper.vm.$nextTick()
+        await wrapper.vm.submitForm()
+
+        setTimeout(() => {
+          expect(actions.AddAsyncJob).toHaveBeenCalled()
+          expect(mocks.$pollJob).toHaveBeenCalled()
+          expect(wrapper.emitted()['close-action'][0]).toEqual([])
+
+          done()
+        })
+      })
+
+      it('check $message.error is called when api is called with throw error', async (done) => {
+        const mockError = {
+          message: 'Error: throw error message'
+        }
+
+        wrapper = factory({
+          props: {
+            resource: {}
+          },
+          data: {
+            selectedHost: {
+              requiresStorageMotion: true,
+              id: 'test-host-id',
+              name: 'test-host-name'
+            }
+          }
+        })
+        jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
+
+        mockAxios.mockRejectedValue(mockError)
+
+        await wrapper.vm.$nextTick()
+        await wrapper.vm.submitForm()
+
+        setTimeout(() => {
+          expect(mocks.$notification.error).toHaveBeenCalled()
+          expect(mocks.$notification.error).toHaveBeenCalledWith({
+            message: i18n.t('message.request.failed'),
+            description: 'Error: throw error message',
+            duration: 0
+          })
+
+          done()
+        })
+      })
+    })
+
+    describe('handleChangePage()', () => {
+      it('check page, pageSize and fetchData() when handleChangePage() is called', () => {
+        wrapper = factory({
+          props: {
+            resource: {}
+          },
+          data: {
+            page: 1,
+            pageSize: 10
+          }
+        })
+        const spyFetchData = jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.handleChangePage(2, 20)
+
+          expect(wrapper.vm.page).toEqual(2)
+          expect(wrapper.vm.pageSize).toEqual(20)
+          expect(spyFetchData).toBeCalled()
+        })
+      })
+    })
+
+    describe('handleChangePageSize()', () => {
+      it('check page, pageSize and fetchData() when handleChangePageSize() is called', () => {
+        wrapper = factory({
+          props: {
+            resource: {}
+          },
+          data: {
+            page: 1,
+            pageSize: 10
+          }
+        })
+        const spyFetchData = jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
+
+        wrapper.vm.$nextTick(() => {
+          wrapper.vm.handleChangePageSize(2, 20)
+
+          expect(wrapper.vm.page).toEqual(2)
+          expect(wrapper.vm.pageSize).toEqual(20)
+          expect(spyFetchData).toBeCalled()
+        })
+      })
+    })
+  })
+})