Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing Vue components that rely on a global event bus

I declare an event bus in my global app.js like so:

window.Event = new Vue();

The component looks like

export default {
    data() {
        return {
            hasError: false,
            zip: '',
        };
    },

    methods: {
        setZip: function() {
            this.hasError = false;
            this.$emit('setZip', this.zip);
        },
    },

    mounted() {
        Event.$on('showErrors', (errors) => {
            this.hasError = errors.zip ? true : false;
        });

        this.zip = this.editZip;
    },

    props: [
        'editZip'
    ],
}

I unit test my components with ava with the following helpers/setup.js:

const browserEnv = require('browser-env');
const hook = require('vue-node');
const { join } = require('path');

// Setup a fake browser environment
browserEnv();

// Pass an absolute path to your webpack configuration to the hook function.
hook(join(__dirname, './webpack.config.js'));

The webpack.config.js looks like:

module.exports = {
    module: {
      loaders: [
          {
            test: /\.vue$/,
            loader: 'vue-loader',
          },
        {
          test: /\.js$/,
            loader: 'babel',
            exclude: /node_modules/,
          },
      ],
    },
  resolve: {
      extensions: ['.js', '.vue'],
      },
};

When running the following test

import Vue from 'vue/dist/vue.js';
import test from 'ava';
import Zip from '../../resources/assets/js/components/Company/Zip.vue';

let vm;

test.beforeEach(t => {
    let Z = Vue.extend(Zip);

    vm = new Z({ propsData: {
        editZip: 1220
    }}).$mount();
});

test('that it renders a div with class form-group', t => {
    t.is(vm.$el.className, 'form-group');
});

it passes, but the following error gets thrown:

[Vue warn]: Error in mounted hook: "TypeError: Event.$on is not a function"

(found in <Root>)
TypeError: Event.$on is not a function
    at VueComponent.mounted (/mnt/c/code/leaflets/resources/assets/js/components/Company/City.vue:107:15)
    at callHook (/mnt/c/code/leaflets/node_modules/vue/dist/vue.js:2530:21)
    at mountComponent (/mnt/c/code/leaflets/node_modules/vue/dist/vue.js:2424:5)
    at VueComponent.Vue$3.$mount (/mnt/c/code/leaflets/node_modules/vue/dist/vue.js:7512:10)
    at VueComponent.Vue$3.$mount (/mnt/c/code/leaflets/node_modules/vue/dist/vue.js:9592:16)
    at Test._ava2.default.beforeEach.t [as fn] (/mnt/c/code/leaflets/tests/js/CompanyCity.js:12:9)
    at Test.callFn (/mnt/c/code/leaflets/node_modules/ava/lib/test.js:281:18)
    at Test.run (/mnt/c/code/leaflets/node_modules/ava/lib/test.js:294:23)
    at runNext (/mnt/c/code/leaflets/node_modules/ava/lib/sequence.js:58:44)
    at Sequence.run (/mnt/c/code/leaflets/node_modules/ava/lib/sequence.js:90:10)
    at Concurrent.run (/mnt/c/code/leaflets/node_modules/ava/lib/concurrent.js:41:37)
    at runNext (/mnt/c/code/leaflets/node_modules/ava/lib/sequence.js:58:44)
    at Sequence.run (/mnt/c/code/leaflets/node_modules/ava/lib/sequence.js:90:10)
    at runNext (/mnt/c/code/leaflets/node_modules/ava/lib/sequence.js:58:44)
    at Sequence.run (/mnt/c/code/leaflets/node_modules/ava/lib/sequence.js:90:10)
    at Bluebird.try (/mnt/c/code/leaflets/node_modules/ava/lib/runner.js:214:48)
    at tryCatcher (/mnt/c/code/leaflets/node_modules/bluebird/js/release/util.js:16:23)
    at Function.Promise.attempt.Promise.try (/mnt/c/code/leaflets/node_modules/bluebird/js/release/method.js:39:29)
    at Runner.run (/mnt/c/code/leaflets/node_modules/ava/lib/runner.js:214:22)
    at process.on.options (/mnt/c/code/leaflets/node_modules/ava/lib/main.js:82:10)
    at emitOne (events.js:96:13)
    at process.emit (events.js:191:7)
    at process.on.message (/mnt/c/code/leaflets/node_modules/ava/lib/process-adapter.js:14:10)
    at emitTwo (events.js:106:13)
    at process.emit (events.js:194:7)
    at process.nextTick (internal/child_process.js:766:12)
    at _combinedTickCallback (internal/process/next_tick.js:73:7)
    at process._tickCallback (internal/process/next_tick.js:104:9)

I can't figure out where to declare the window.Event = new Vue() in my test, so that the tested component can access the Event variable.

like image 255
mazedlx Avatar asked May 10 '17 07:05

mazedlx


People also ask

What should I unit test at Vue?

Unit tests are the smallest and most simple of tests - they make assertions on the smallest units of work, isolating each part of a single component. Snapshot tests save the markup of your Vue component, and compare to the new one generated each time the test runs.

What is event bus in Vue?

An Event Bus is nothing but a global Vue instance that is imported by the components involved in communication and passing data. It makes use of the $on, $emit, and $off properties of the Vue object to emit out events and pass on data.

Which testing framework is best for Vue?

1. Component Testing with Vue Test Utils & Jest. Jest is a well-liked JavaScript testing framework that comes packed with many goodies for developers focusing on simplicity. It's one of the fastest testing frameworks for Vue single-file components.

What is e2e testing Vue?

End-to-end tests do not import any of your Vue application's code, but instead rely completely on testing your application by navigating through entire pages in a real browser. End-to-end tests validate many of the layers in your application.


1 Answers

First of all, I'd recommend to remove the global EventBus from the window object (if you can), as it's not often ideal to rely on global props that could get overwritten by some other parts of the code leading to undesired effects.

You can just create a event-bus.js file and create the event bus in there:

import Vue from 'vue';
export const EventBus = new Vue();

Then you can just import that file from any other component that needs access to the event bus like

import {EventBus} from '/path/to/event-bus.js'

From there, you can use it exactly as you're currently using it.

However, if you have to keep your EventBus in the window object for some reason, you'll have to also mock it so you can test it.

First, when you call the browserEnv function, you'll need to mock the window object by doing this:

browserEnv(['window']);

Then, window will be available as a global variable, but you will need to change the implementation of your component to explicitly grab the EventBus from the window object, like:

window.EventBus

You can also define it globally in your component like:

const { EventBus } = window

Let me know if this works.

Thanks,

like image 121
Jair Reina Avatar answered Oct 15 '22 11:10

Jair Reina