Vite Plugin for Vue 3 testing
Tags: vue, javascript, testing, plugin
Published: 2020-10-07 10:00:55

![Alt Text](https://raw.githubusercontent.com/aelbore/aria-test/master/Test.png)

## Why need test?
* Get an error if we break code
* Save time (don't need to test manually)
  - It speeds up development because you don’t have to test everything manually after every change.
* Integrate into build workflow
* Improve your code

**[Vite](https://github.com/vitejs/vite)** is an opinionated web dev build tool that serves your code via native ES Module imports during dev and bundles it with Rollup for production.

**[Vue-Test-Utils](https://vue-test-utils.vuejs.org/)** is a utilities for testing Vue components

## Table Of Contents
   * [Getting Started](#getting-started)
   * [Adding Test](#adding-test)
   * [Create a Vite Plugin](#vite-plugin)

### Getting Started <a name="getting-started"></a>
 * Lets create a folder `mkdir vue-testing`
 * `cd vue-testing` then `npm init -y`
 * Install dependencies
 ```
 npm install vue@3.0.0
 npm install vite @vue/test-utils@^2.0.0-beta.5 --save-dev
 ```
* Create `./src/App.vue`
 ```vue
 <template>
   <h1>
     Hello world!
   </h1>
 </template>

 <script>
 export default { 
  name: 'App'
 }
 </script>

  ```
* Create `./src/main.js` root director
```javascript
import {createApp} from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
```
* Add `index.html`
```html
 <!DOCTYPE html>
 <html lang="en">
 </head>
   <body>
     <div id="app"></div>
     <script type="module" src="./src/main.js"></script>
   </body>
 </html>
```
* Update or Add scripts to your `package.json` file
```
  "scripts": {
    "serve": "vite",
    ....
  }
```
* Now we can run our application to make sure everything is working.
```
npm run serve
```

### Adding Test to your application <a name="adding-test"></a>

* Lets create a folder `mkdir test`
* `cd test`
* Create `./test/App.spec.js`
```javascript
import { mount } from '@vue/test-utils'
import App from '../src/App.vue'

 describe('App.spec.js', () => {
  it('renders', () => {
    const wrapper = mount(App, { attachTo: '#root' })
    expect(wrapper.html()).to.contain('Hello')
  })
 })
```
* Create `./test/index.html`
```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue Testing</title>
  <link href="https://unpkg.com/mocha@4.0.1/mocha.css" rel="stylesheet"/>
  <script src="https://unpkg.com/chai@4.1.2/chai.js"></script>
  <script src="https://unpkg.com/mocha@4.0.1/mocha.js"></script>
  <style>
    #meta > * {
      margin: 20px 50px 0 50px;
      font-weight: 200;
      font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    }

    #meta > h1 { font-size: 2em; }
    #meta > h2 { font-size: 1.5em; }

    #root {
      margin: 20px 50px 0 50px;
    }
  </style>
  <script>
    mocha.setup('bdd')
    window.expect = chai.expect
  </script>
</head>
<body>
  <div id="meta">
    <h2>Mocha HTML Reporter</h2>
    <div>
      <div id="mocha" style="margin: 0;"></div>
    </div>
  </div>

</body>
</html>
```
* Add setup 
```javascript
  beforeEach(() => {
    delete window["__VUE_DEVTOOLS_TOAST__"]
  
    const existingRoot = document.getElementById('root')
    if (existingRoot) {
      existingRoot.innerHTML = ''
      return
    }
    const root = document.createElement('div')
    root.id = 'root'
    document.body.appendChild(root)
  })

  afterEach(() => {
    document.getElementById('root').remove()
  })
```
* Load all spec or test files
```javascript
import('./test/App.spec.js').then(() => {
  mocha.setup({ reporter: 'html' })
  mocha.checkLeaks()
  mocha.run()
})
```
* Run the test
```
npm run serve
```
* Go to `http://localhost:3000/test`
* Open the browser console, you will have expected error, vite can only process es module, and @babel/parser is commonjs module
![Alt Text](https://raw.githubusercontent.com/aelbore/aria-test/master/error-babel-parser.png)
* Vite under the hood uses rollup to build Single File Component `SFC`, so lets use rollup to convert `@babel/parser` to es module
* Create a file `./tools/babel-parser.js`
```javascript
export { parse } from '@babel/parser'
```
* Create `rollup.config.js`
```javascript
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'

export default {
  input: './tools/babel-parser.js',
  plugins: [  commonjs(), resolve() ],
  output: {
    file: './node_modules/@babel/parser/index.js',
    format: 'es',
    sourcemap: true
  }
}
```
* Also create `vite.config.js` to create alias for `@babel/parser` and to use it later
```javascript
export default {
  alias: {
    '@babel/parser': '@babel/parser/index.js'
  }
}
```
* Add `npm script` to run the rollup
```json
 "scripts": {
    ...
    "optimize": "rollup -c"
  },
```
* Run `npm run optimize` and `npm run serve`
![Alt Text](https://raw.githubusercontent.com/aelbore/aria-test/master/html-test.png)


### Create a Vite Plugin <a name="vite-plugin"></a>
* Create file `./tools/testPlugin.js`
```javascript
import Router from 'koa-router'
import fs from 'aria-fs'

function getTestFiles() {
  const router = new Router()

  return ({ app }) => {

    router.get('/test-files', async (ctx, next) => {
      ctx.body = await fs.globFiles(`./test/**/*.spec.js`, true)
      await next()
    })

    app.use(router.routes())
  }
}

export default function testPlugin() {
  return {
    configureServer: [ getTestFiles() ]
  }
}
```
* Install dependencies
```bash
npm install koa-router aria-fs --save-dev
```
* Update `vite.config.js`
```javascript
import testPlugin from './tools/testPlugin'

export default {
  alias: {
    '@babel/parser': '@babel/parser/index.js'
  },
  plugins: [ testPlugin() ]
}
```
* Update `script` in `./test/index.html`
```javascript
(async function() {

  beforeEach(() => {
    delete window["__VUE_DEVTOOLS_TOAST__"]
  
    const existingRoot = document.getElementById('root')
    if (existingRoot) {
      existingRoot.innerHTML = ''
      return
    }
    const root = document.createElement('div')
    root.id = 'root'
    document.body.appendChild(root)
  })

  afterEach(() => {
    document.getElementById('root').remove()
  })

  const response = await fetch('/test-files')
  const files = await response.json()

  await Promise.all(files.map(file => import(file)))

  mocha.setup({ reporter: 'html' })
  mocha.checkLeaks()
  mocha.run()
})()
```

* Let's add another test `Button` increment, file `./src/Button.vue`
```vue
<template>
  <div>
    <p>Times clicked: {{ count }}</p>
    <button @click="increment">increment</button>
  </div>
</template>

<script>
  export default {
    name: 'Button',
    data: () => ({
      count: 0,
    }),
    methods: {
      increment() {
        this.count++
      },
    },
  }
</script>
```
* And test for our `Button`, `./tools/Button.spec.js`
```javascript
import { mount } from '@vue/test-utils'
import Button from '../src/Button.vue'

 describe('Button.spec.js', () => {
  it('increments value on click', async () => {
    const wrapper = mount(Button, { attachTo: '#root' })
    const button = wrapper.find('button')
    
    await button.trigger('click')
    expect(wrapper.find('p').html()).to.contain('Times clicked: 1')
  })
 })
```
* Run `npm run serve` and go to `http://localhost:3000/test`
![Alt Text](https://raw.githubusercontent.com/aelbore/aria-test/master/html-report.png)