# Testing

# Vue Test Utils

Vue Test Utils (opens new window) is the official library for Vue. Nothing much to say, that gives us no reason to avoid it.

And we need to choose a test runner for VueTestUtils. E.g: Jest, Mocha, Karma, ...

# with Jest

Jest (opens new window) is a delightful JavaScript Testing Framework with a focus on simplicity.

  • sets up JSDOM by default
  • provides built-in assertions
  • great command line user experience

# Install

npm install --save-dev jest @vue/test-utils vue-jest babel-jest

NOTICE

Note: If project is using Babel 7 or higher, need to add babel-bridge to devDependencies

npm install --save-dev babel-core@^7.0.0-bridge.0

# Configuration

Create file jest.config.js for configuration (opens new window)

module.exports = {
    "moduleFileExtensions": [
      "js",
      "vue"
    ],
    "transform": {
        ".*\\.(vue)$": "vue-jest",
        "^.+\\.js$": "babel-jest"
    },

    /// ... more config
}

Webpack Alias

"moduleNameMapper": {
    "^@/(.*)$": "<rootDir>/src/$1"
},

Coverage Config

"collectCoverageFrom": [
    "src/Components/**/*.{js,vue}",
    "!**/node_modules/**",
    "!**/**/index.js"
],
"coverageThreshold": {
    "global": {
        "branches": 20,
        "functions": 20,
    },
    "src/**/*.vue": {
        "branches": 60,
        "statements": 60
    },
}

List files that run before each test file

"setupFilesAfterEnv": [
    './jest.setup.js'
],

Content of jest.setup.js could be:

// alias function
global.before = global.beforeAll
// load 3rd libraries into window of jsdom
const fs = require('fs');
const path = require('path');
let basePath = path.resolve(__dirname, ASSET_PATH);

// function load script in window
const loadedScripts = { };
global.loadScriptInWindow = function loadScriptInWindow(filePath) {

    const window =  global.window;
    if (!filePath.startsWith(basePath)) {
        filePath = basePath + '/' + filePath;
    }

    if (!fs.existsSync(filePath)) {
        console.error('Not found file to load');
    }

    if (!loadedScripts[filePath]) {
        loadedScripts[filePath] = true;
    }

    let content = fs.readFileSync(filePath, { encoding: "utf-8" });

    const scriptEl = window.document.createElement("script");
    scriptEl.textContent = content;
    window.document.body.appendChild(scriptEl);
}

// default loading
loadScriptInWindow('assets/ui/moment.min.js');
loadScriptInWindow('assets/ui/jquery.min.js');
loadScriptInWindow('assets/ui/underscore.js');

# Run

Add few scripts options in package.json for shortcut

"scripts": {
    // ... more scripts
    "test": "jest --verbose",
    "test:dev": "jest --verbose --watchAll",
    "coverage": "jest --coverage",
    "coverage:dev": "jest --coverage --watchAll"
}

Usage:

  • Run once: npm run test
  • Run with watch: npm run test:dev
  • Run coverage once: npm run coverage
  • Run coverage with watch: npm run coverage:dev

# Write test file =))

Here is the API list (opens new window) that can help you

Example:

We have Text.vue

<template>
    <component 
        :is="inputTag" 
    />
</template>

<script>
export default {
    props: {
        isMultiRows: VueTypes.bool.def(false),
    }
    computed: {
        inputTag() {
            return this.isMultiRows ? 'textarea' : 'input';
        },
    },
}
</script>
import { createComponent } from '@/../tests/utils'

import Text from './Text.vue'

describe('Text.vue', () => {
    let wrapper

    afterEach(() => {
        wrapper.destroy()
    })

    it('renders the correct markup', async () => {
        wrapper = createComponent(Text)
        expect(wrapper.element.tagName).toBe('INPUT')

        await wrapper.setProps({ isMultiRows: true })
        expect(wrapper.element.tagName).toBe('TEXTAREA')
    })

})

# Utils

Helpers to create Wrapper

import { shallowMount, mount } from '@vue/test-utils'

export const createComponent = (VueComponent, props = {}, settings = {} ) => {
    let copySettings = Object.assign({}, { shallow: true, attach: false }, settings)
    let mountFn = !!copySettings.shallow ? shallowMount : mount

    delete copySettings.shallow
    delete copySettings.attach

    let mountOptions = {
        propsData: { ...props },
        ...copySettings
    }

    if (!!settings.attach) {
        mountOptions.attachTo = createContainer()
    }

    return mountFn(VueComponent, mountOptions)
}

export const createContainer = (tag = 'div') => {
    const container = document.createElement(tag)
    document.body.appendChild(container)
    return container
}