Categories
Vue Testing

Unit Test Vue Apps with Vue Test Utils — Vue Router and Vuex Mocks

With the Vue Test Utils library, we can write and run unit tests for Vue apps easily.

In this article, we’ll look at how to write unit tests with the Vue Test Utils library.

Mocking $route and $router

We can mock the $route and $router objects to inject the Vue Router’s reactive properties into our mounted component.

For instance, we can write:

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

const Component = {
  template: `
    <div>
      <p>{{$route.path}}</p>
    </div>
  `
}

describe('Component', () => {
  it('renders the Component component with the path', async () => {
    const $route = {
      path: '/some/path'
    }
    const wrapper = shallowMount(Component, {
      mocks: {
        $route
      }
    })
    expect(wrapper.text()).toContain('/some/path')
  })

})

We have the Component component that renders the $route.path value.

We just put that in the mocks property so that it’ll be set with the given value when we mount the component.

Testing Vuex in Components

We can test components that depends on Vuex.

For example, if we have a Vue app that has the following code:

main.js

import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex'

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

Vue.config.productionTip = false

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')

App.vue

<template>
  <div id="app">
    <button @click="$store.dispatch('increment')">add</button>
    <p>{{count}}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  computed: {
    count() {
      return this.$store.state.count;
    },
  },
};
</script>

Then we can test the code by writing:

example.spec.js

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import App from '@/App'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('App.vue', () => {
  let actions
  let store

  beforeEach(() => {
    actions = {
      increment: jest.fn(),
    }
    store = new Vuex.Store({
      actions
    })
  })

  it('dispatches "increment" action when button is clicked', async () => {
    const wrapper = shallowMount(App, { store, localVue })
    const button = wrapper.find('button')
    await button.trigger('click')
    expect(actions.increment).toHaveBeenCalled()
  })
})

Once again, we call createLocalVue to create a local Vue instance we use for testing.

Then we have a beforeEach callback to set up the store and inject it into our app.

We inject the mocked store and the localVue object.

Then we get the button and trigger the click event on it.

And finally, we check that the increment action has been triggered after clicking the button.

Mocking Getters

Similarly, we can mock getters in our tests.

Given that we have the following code:

main.js

import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex'

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    increment(context) {
      context.commit('increment')
    }
  },
  getters: {
    count(state) {
      return state.count;
    }
  }
})

Vue.config.productionTip = false

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')

App.vue

<template>
  <div id="app">
    <button @click="$store.dispatch('increment')">add</button>
    <p>{{count}}</p>
  </div>
</template>

<script>
import { mapGetters } from "vuex";

export default {
  name: "App",
  computed: {
    ...mapGetters(['count'])
  },
};
</script>

Then to test our code, we write:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import App from '@/App'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('App.vue', () => {
  let actions
  let store
  let getters

  beforeEach(() => {
    getters = {
      count: () => 2,
    }
    actions = {
      increment: jest.fn(),
    }
    store = new Vuex.Store({
      actions,
      getters
    })
  })

  it('dispatches "increment" action when button is clicked', () => {
    const wrapper = shallowMount(App, { store, localVue })
    expect(+wrapper.find('p').text()).toBe(getters.count())
  })
})

We add the mock getter into out mock store object.

Then we get the p element’s text and sees if it matches what’s returned in the mocked getter.

Conclusion

We can mock Vuex and Vue Router in our tests so that our components can be tested in isolation.

Categories
Vue Testing

Unit Test Vue Apps with Vue Test Utils — Mock External Dependencies

With the Vue Test Utils library, we can write and run unit tests for Vue apps easily.

In this article, we’ll look at how to write unit tests with the Vue Test Utils library.

Test Async Behavior Outside of Vue

To test async behavior outside of Vue, we have to mock the async code before we run our test.

For example, if we have a YesNo.vue component in the components folder:

<template>
  <div>
    <button @click="fetchResults">fetch answer</button>
    <p>{{ answer }}</p>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  name: "YesNo",
  data() {
    return {
      answer: undefined
    }
  },
  methods: {
    async fetchResults() {
      const { data: { answer } } = await axios.get('https://yesno.wtf/api')
      this.answer = answer;
    }
  }
};
</script>

We can test it by mocking the axios object and put our own get method in the mocked object:

import { shallowMount } from '@vue/test-utils'
import YesNo from '@/components/YesNo'

jest.mock('axios', () => ({
  get: () => Promise.resolve({ data: { answer: 'yes' } })
}))

describe('YesNo tests', () => {
  it('renders answer after clicking fetch answer', async () => {
    const wrapper = shallowMount(YesNo)
    await wrapper.find('button').trigger('click')
    expect(wrapper.text()).toContain('yes')
  })

})

Since axios.get returns a promise with the response, we do the same thing with our mock get method.

Then when we click the button, we should see the 'yes' answer.

Using with Vue Router

We can test our Vue app with Vue Router.

For example, given that we have the following Vue app code:

components/Bar.vue

<template>
  <div>Bar</div>
</template>

<script>
export default {
  name: "Bar",
};
</script>

components/Foo.vue

<template>
  <div>Foo</div>
</template>

<script>
export default {
  name: "Foo",
};
</script>

main.js

import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import Foo from '@/components/Foo';
import Bar from '@/components/Bar';

const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

const router = new VueRouter({
  routes
})

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

We can test the Foo component by writing:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'
import Foo from '@/components/Foo';

const localVue = createLocalVue()
localVue.use(VueRouter)
const router = new VueRouter()

describe('Foo tests', () => {
  it('renders the Foo component', async () => {
    const wrapper = shallowMount(Foo, {
      localVue,
      router
    })
    expect(wrapper.text()).toContain('Foo')
  })

})

We create a local Vue instance with the createLocalVue function.

Then we call use to add the VueRouter plugin.

Also, we create a new VueRouter instance.

Then we pass that all in when we mount our component so that we can test it in isolation.

Testing components that use router-link or router-view

There are a few ways to test components that use the router-link or router-view components.

One way is to stub those components by putting the component names in the stubs array:

import { shallowMount } from '@vue/test-utils'
import App from '@/App';

describe('App tests', () => {
  it('renders the App component', async () => {
    const wrapper = shallowMount(App, {
      stubs: ['router-link', 'router-view']
    })
    expect(wrapper.find('app')).toBeTruthy()
  })

})

We mount our App component from the previous example code with the stub for the router-link and router-view components.

Then we can carry on with our tests.

We can also pass in the localVue object with the local version of the Vue instance that has the VueRouter instance added to it:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'
import App from '@/App';

const localVue = createLocalVue()
localVue.use(VueRouter)

describe('App tests', () => {
  it('renders the App component', async () => {
    const wrapper = shallowMount(App, {
      localVue
    })
    expect(wrapper.find('app')).toBeTruthy()
  })

})

Conclusion

We can test our app with external dependencies by mocking them so that they can run reliably without relying on external resources.

Categories
Vue Testing

Unit Test Vue Apps with Vue Test Utils — Keyboard and Mouse Events

With the Vue Test Utils library, we can write and run unit tests for Vue apps easily.

In this article, we’ll look at how to write unit tests with the Vue Test Utils library.

Keyboard Event Tests

We can test keyboard events in our code.

For example, we can write:

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

const KEY_DOWN = 40
const KEY_UP = 38
const ESCAPE = 27

const QuantityComponent = {
  template: `
    <div>
      <input type="text" [@keydown](http://twitter.com/keydown "Twitter profile for @keydown").prevent="onKeydown" v-model="quantity" />
    </div>
  `,
  data() {
    return {
      quantity: 0
    }
  },
  methods: {
    increment() {
      this.quantity += 1
    },
    decrement() {
      this.quantity -= 1
    },
    clear() {
      this.quantity = 0
    },
    onKeydown(e) {
      if (e.keyCode === ESCAPE) {
        this.clear()
      }
      if (e.keyCode === KEY_DOWN) {
        this.decrement()
      }
      if (e.keyCode === KEY_UP) {
        this.increment()
      }
      if (e.key === 'a') {
        this.quantity = 10
      }
    }
  },
  watch: {
    quantity(newValue) {
      this.$emit('input', newValue)
    }
  }
}

describe('Key event tests', () => {
  it('Quantity is zero by default', () => {
    const wrapper = mount(QuantityComponent)
    expect(wrapper.vm.quantity).toBe(0)
  })

  it('Up arrow key increments quantity by 1', async () => {
    const wrapper = mount(QuantityComponent)
    await wrapper.find('input').trigger('keydown.up')
    expect(wrapper.vm.quantity).toBe(1)
  })

  it('Down arrow key decrements quantity by 1', async () => {
    const wrapper = mount(QuantityComponent)
    wrapper.vm.quantity = 2
    await wrapper.find('input').trigger('keydown.down')
    expect(wrapper.vm.quantity).toBe(1)
  })

  it('Escape sets quantity to 0', async () => {
    const wrapper = mount(QuantityComponent)
    wrapper.vm.quantity = 3
    await wrapper.find('input').trigger('keydown.esc')
    expect(wrapper.vm.quantity).toBe(0)
  })

  it('Magic character "a" sets quantity to 10', async () => {
    const wrapper = mount(QuantityComponent)
    await wrapper.find('input').trigger('keydown', {
      key: 'a'
    })
    expect(wrapper.vm.quantity).toBe(10)
  })
})

to trigger the keyboard events on each test.

The QuantityComponent has an input element that listens to the keydown event and updates the quantity accordingly.

The onKeydown method updates the quantity reactive property to set the value according to the key that’s pressed.

In our tests, we get the input element with the find method.

Then we call trigger to trigger various kinds of keydown events.

keydown.up simulates pressing the up arrow.

keydown.up simulates pressing the down arrow.

keydown.up simulates pressing the esc key.

The last test simulates pressing the 'a' key.

Once we did that, we can check the quantity reactive property to get the final result.

Testing Async Behavior

We can test async behavior by using the same principles as the previous example.

For example, we can write:

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

const Counter = {
  template: `
    <div>
      <button @click='count++'>add</button>
      <p>{{count}}</p>
    </div>
  `,
  data() {
    return {
      count: 0
    }
  },
}

describe('Counter tests', () => {
  it('count is set to 1 after click', async () => {
    const wrapper = mount(Counter)
    expect(wrapper.text()).toContain('0')
    const button = wrapper.find('button')
    await button.trigger('click')
    expect(wrapper.text()).toContain('1')
  })

})

We have the Counter component that updates the count reactive property when we click on the add button.

Then in our test, we check the rendered content with the text method.

And then we click the button and do the check again.

Conclusion

We can simulate clicks and keyboard events in our tests and test the rendered content.

Categories
Vue Testing

Unit Test Vue Apps with Vue Test Utils — Transitions and Plugin Tests

With the Vue Test Utils library, we can write and run unit tests for Vue apps easily.

In this article, we’ll look at how to write unit tests with the Vue Test Utils library.

Mocking Transitions

We can test components with transitions the same way we test other components.

All we have to do is to set the reactive properties to the value we want.

For example, we can write:

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

const Foo = {
  template: `
    <div>
      <transition>
        <p v-if="show">Foo</p>
      </transition>
    </div>
  `,
  data() {
    return {
      show: true
    }
  }
}

describe('Foo', () => {
  it('renders bar for foo prop', async () => {
    const wrapper = mount(Foo)
    expect(wrapper.text()).toMatch(/Foo/)
    await wrapper.setData({
      show: false
    })
    expect(wrapper.text()).not.toMatch(/Foo/)
  })
})

We call mount to mount the Foo component.

Then we check what’s displayed before we set the show reactive property to false .

Then we call setData to set show to false .

Then we check the pattern of the content displayed with the text method to return the rendered text and the toMatch method to check whether the content matches the given regex pattern.

We can also stub the transitions with the transitionStub helper:

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

const Foo = {
  template: `
    <div>
      <transition>
        <p v-if="show">Foo</p>
      </transition>
    </div>
  `,
  data() {
    return {
      show: true
    }
  }
}

const transitionStub = () => ({
  render(h) {
    return this.$options._renderChildren
  }
})

describe('Foo', () => {
  it('renders bar for foo prop', async () => {
    const wrapper = mount(Foo, {
      stubs: {
        transition: transitionStub()
      }
    })
    expect(wrapper.text()).toMatch(/Foo/)
    await wrapper.setData({
      show: false
    })
    expect(wrapper.text()).not.toMatch(/Foo/)
  })
})

transitionStub is just a function that returns an object with the render method to simulate rendering the transitions.

We can also avoid using setData if we mount our component and set the reactive properties’ values when we mount it:

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

const Foo = {
  template: `
    <div>
      <transition>
        <p v-if="show">Foo</p>
      </transition>
    </div>
  `,
  data() {
    return {
      show: true
    }
  }
}

test('should render Foo', async () => {
  const wrapper = mount(Foo, {
    data() {
      return {
        show: true
      }
    }
  })

  expect(wrapper.text()).toMatch(/Foo/)
})

test('should not render Foo', async () => {
  const wrapper = mount(Foo, {
    data() {
      return {
        show: false
      }
    }
  })

  expect(wrapper.text()).not.toMatch(/Foo/)
})

Applying Global Plugins and Mixins

If we want to apply global plugins, then we have to use the createLocalVue to return a mocked version of the Vue constructor.

This way, we can control when the global plugin is applied.

For example, we can write:

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

const localVue = createLocalVue()

const MyPlugin = {};
MyPlugin.install = function (Vue, options) {
  Vue.prototype.fooMethod = function () {
    this.foo = 'bar';
  }
}

localVue.use(MyPlugin);
const Foo = {
  template: `
    <div>
      <p>{{foo}}</p>
    </div>
  `,
  data() {
    return {
      foo: ''
    }
  }
}

test('should render bar', async () => {
  const wrapper = mount(Foo, {
    localVue
  })
  await wrapper.vm.fooMethod();
  expect(wrapper.text()).toContain('bar')
})

We call the createLocalVue function to create a mock version of the Vue constructor.

Then we create our plugin by defining the MyPlugin object and adding the fooMethod instance method to our plugin.

Next, we call localVue.use(MyPlugin) so we can add our plugin with we mount the Foo component.

Then in our test, we set the localVue property in the object in the 2nd argument of mount .

And we call our fooMethod to run the test.

Finally, we check if the text is rendered.

Conclusion

We can mock transitions and plugins so that we can do test with plugins.

Categories
Vue Testing

Unit Test Vue Apps with Vue Test Utils — Mocking Items

With the Vue Test Utils library, we can write and run unit tests for Vue apps easily.

In this article, we’ll look at how to write unit tests with the Vue Test Utils library.

Mocking Injections

We can mock injected reactive properties by passing them all in the mocks property when we mount our component.

For example, we can write:

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

const Foo = {
  template: `
    <div>
      <p>{{$route.path}}</p>
    </div>
  `,
}

const $route = {
  path: '/foo',
}

test('should render the path', () => {
  const wrapper = mount(Foo, {
    mocks: {
      $route
    }
  })
  expect(wrapper.text()).toContain('/foo')
})

We just set the $route property to the $route object in the mocks object then we should see it rendered.

Stubbing Components

We can stub global components by writing using the stubs property:

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

const Foo = {
  template: `
    <div>
      <child-component></child-component>
    </div>
  `,
}

test('should render child-component', () => {
  const wrapper = mount(Foo, {
    stubs: ['child-component']
  })
  expect(wrapper.find('child-component')).toBeTruthy()
})

We have the child-component in Foo which we didn’t register, so we can stub it by using the stubs property to stub it with an empty component.

Testing Key, Mouse, and Other DOM Events

We can trigger events on component with the trigger method.

For example, we can write:

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

const MyButton = {
  template: `
    <button @click='count++'>
      {{count}}
    </button>
  `,
  data() {
    return {
      count: 0
    }
  }
}

test('shows latest count when button is clicked', async () => {
  const wrapper = mount(MyButton)
  await wrapper.trigger('click')
  expect(wrapper.text()).toContain('1');
})

We mount the MyButton component and call trigger on the returned component wrapper.

Then we can check the latest value of count with the text method.

For example, we can write:

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

const MyButton = {
  template: `
    <div>
      <button @click='count++'>
        add
      </button>
      <p>{{count}}</p>
    </div>
  `,
  data() {
    return {
      count: 0
    }
  }
}

test('shows latest count when button is clicked', async () => {
  const wrapper = mount(MyButton)
  await wrapper.find('button').trigger('click')
  expect(wrapper.find('p').text()).toContain('1');
})

to create the MyButton component with the add button.

It updated the count when we click it.

To test it, we call find to find the button. Then we call trigger to trigger the click event.

Then we call find with the 'p' selector to check the content of the p element.

We can check if a method is called when we trigger an event on an element.

For example, we can write:

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

const Foo = {
  template: `
    <div>
      <button class="yes" @click="callYes">Yes</button>
      <button class="no" @click="callNo">No</button>
    </div>
  `,
  props: {
    callMe: {
      type: Function
    }
  },
  methods: {
    callYes() {
      this.callMe('yes')
    },
    callNo() {
      this.callMe('no')
    }
  }
}

test('should call callMe with the right arguments when buttons are clicked', async () => {
  const spy = jest.fn();
  const wrapper = mount(Foo, {
    propsData: {
      callMe: spy
    }
  })
  await wrapper.find('button.yes').trigger('click')
  expect(spy).toHaveBeenCalledWith('yes');
  await wrapper.find('button.no').trigger('click')
  expect(spy).toHaveBeenCalledWith('no');
})

We have the Foo component that takes a callMe function as a prop.

Then to write our test, we create a spy of the callMe function by calling jest.fn() .

Next, we pass that in as a prop with we mount Foo .

Finally, we call trigger to trigger clicks on the buttons and check the rendered results.

toHaveBeenCalledWith takes the arguments of the function we’re expecting.

Conclusion

We can mock various items in our components with Jest and Vue Test Utils.