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.

Categories
Vue Testing

Unit Test Vue Apps with Vue Test Utils — Lifecycle and Async 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.

Lifecycle Hooks

When we call mount or shallowMount to mount our components, the lifecycle methods will be run.

However, the beforeDestroy or destroyed methods won’t be triggered unless the component is manually destroyed with wrapper.destroy() .

The component won’t be automatically destroyed at the end of each test, so we have to clean up any dependencies manually.

Writing Asynchronous Tests

When we write tests, we have to be aware that any changes to reactive properties are done asynchronously.

This means that we have to the test as an async function or use then and run our test code that runs after the reactive property is updated in the then callback.

For example, we can write:

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

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

describe('Counter', () => {
  it('renders new number when Add up button is clicked', async () => {
    const wrapper = shallowMount(Counter)
    const button = wrapper.find('button')
    const text = wrapper.find('p')
    await button.trigger('click')
    expect(text.text()).toBe('clicks: 1')
  })
})

to await the trigger method so that we can check the updated value of the count reactive property after the update is done.

We can also write:

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

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

describe('Counter', () => {
  it('renders new number when Add up button is clicked', () => {
    const wrapper = shallowMount(Counter)
    const button = wrapper.find('button')
    const text = wrapper.find('p')
    button.trigger('click')
      .then(() => {
        expect(text.text()).toBe('clicks: 1')
      })
  })
})

which is the same code without async and await.

Asserting Emitted Events

We can check which events are emitted when we do something with the component.

To do this, we can call the emitted method to check which events are emitted.

For instance, we can write:

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

const Foo = {
  template: `
    <div>
      <button @click="$emit('foo')">emit foo</button>
    </div>
  `,
}

describe('Foo', () => {
  it('emits foo event when the emit foo button is clicked', async () => {
    const wrapper = shallowMount(Foo)
    const button = wrapper.find('button');
    await button.trigger('click')
    expect(wrapper.emitted().foo).toBeTruthy()
  })
})

We have the Foo component that calls $emit to emit the 'foo' event when we click it.

Then in the last line of the test, we call wrapper.emitted() to check if the foo event is emitted.

If it’s emitted, then the foo property should be truthy.

We can also check which payload is emitted. For example, we can write:

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

const Foo = {
  template: `
    <div>
      <button @click="$emit('foo', 123)">emit foo</button>
    </div>
  `,
}

describe('Foo', () => {
  it('emits foo event with payload 123 when the emit foo button is clicked', async () => {
    const wrapper = shallowMount(Foo)
    const button = wrapper.find('button');
    await button.trigger('click')
    expect(wrapper.emitted().foo.length).toBe(1)
    expect(wrapper.emitted().foo[0]).toEqual([123])
  })
})

We check the foo property’s length to see how many times it’s emitted.

And the payload is checked by accessing foo ‘s array value and checking the content.

Conclusion

We can check if events are emitted and be aware that lifecycle hooks like destroy aren’t run automatically.