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.

Categories
Vue Testing

Unit Test Vue Apps with Vue Test Utils

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.

Installation

We can add it when we run Vue CLI to scaffold our app.

We select Unit Tests from the list and then select Jest to add the test framework with the production code.

Usage

Once we added the test files, we can write our unit tests.

In our unit tests, we can interact with our components like we do when using them.

To do that, we mount the component and interact with the elements by triggering events on them.

Also, we can mock any data and external resources that need to be set to let us do the tests.

This way, we can run our tests independently without relying on anything external that makes the tests unreliable.

To write our first test, we can add the test in the tests/unit folder by writing:

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

const MessageComponent = {
  template: '<p>{{ msg }}</p>',
  props: ['msg']
}

describe('MessageComponent', () => {
  it('renders props.msg when passed', () => {
    const msg = 'new message'
    const wrapper = shallowMount(MessageComponent, {
      propsData: { msg }
    })
    expect(wrapper.text()).toMatch(msg)
  })
})

We have the MessageComponent that renders the value of the msg prop onto the screen.

In our test, which is in the callback we pass into it , we call shallowMount to mount the component without its children.

propsData lets us pass the prop data we need for the test.

Then the wrapper.text() method returns the rendered text.

And we use toMatch to check the results.

To run the test, we run npm run test:unit to run the unit test and then we should see some green text to indicate that the test passed.

Simulating User Interaction

We can also simulate user interaction easily.

To do that, 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')
  })
})

We have the Counter component that we want to test.

It has a button that increases count by 1 when we click it.

Then in the test, we call shallowMount to mount the component.

Then we call wrapper.find to get the button and the p element.

To simulate a click on the button, we call trigger to trigger the click event.

trigger is async is returns a promise, so our test callback has to be an async function and we have to await the trigger method.

Then we check the text displayed in the p element with the text method and see if the text has been updated.

Conclusion

We can write unit tests easily to test Vue apps by using Jest and simulating user actions.

Categories
Vue Testing

Unit Test Vue Apps with Vue Test Utils — Child Components and Reactive Properties

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.

Emitting Event from Child Component

We can check for events that are emitted in a child component.

To do that, we call mount to mount the parent component with the child component.

Then we can emit the event we want on the child:

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

const ChildComponent = {
  template: `
    <div>
      <p>child</p>
    </div>
  `,
}

const ParentComponent = {
  template: `
    <div>
      <child-component @custom="onCustom" />
      <p id='emitted' v-if="emitted">emitted</p>
    </div>
  `,
  data() {
    return { emitted: false }
  },
  methods: {
    onCustom() {
      this.emitted = true
    }
  },
  components: {
    ChildComponent
  }
}

describe('ParentComponent', () => {
  it('shows emitted when child-component emits custom event', async () => {
    const wrapper = mount(ParentComponent)
    await wrapper.findComponent(ChildComponent).vm.$emit('custom')
    expect(wrapper.find('#emitted').text()).toContain('emitted')
  })
})

To mount the child component of ParentComponent with the ParentComponent , we call the mount method.

The findComponent method has ChildComponent as the argument so that we can use the returned component wrapper to emit the custom event with the $emit method.

Then to check if the ‘emitted’ text is displayed, we call the find method to check if it has the 'emitted' string.

Manipulating Component State

We can manipulate component state with the setData or setProps method.

For example, we can write:

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

const Counter = {
  template: `
    <div>
      <p>{{count}}</p>
    </div>
  `,
  data() {
    return { count: 0 }
  },
}

describe('Counter', () => {
  it('renders 10 for count', async () => {
    const wrapper = mount(Counter)
    await wrapper.setData({ count: 10 })
    expect(wrapper.find('p').text()).toContain(10)
  })
})

to call the setData method with the data we want to set.

The key is the key of a reactive property and the value is its value.

To set props, we can use the setProps method:

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

const Foo = {
  template: `
    <div>
      <p>{{foo}}</p>
    </div>
  `,
  props: ['foo']
}

describe('Foo', () => {
  it('renders bar for foo prop', async () => {
    const wrapper = mount(Foo)
    await wrapper.setProps({ foo: 'bar' })
    expect(wrapper.find('p').text()).toContain('bar')
  })
})

We call the setProps method to set the value of the foo prop to 'bar' .

Then we check that if that is rendered in the last line of the test.

Props can also be set when we mount the component:

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

const Foo = {
  template: `
    <div>
      <p>{{foo}}</p>
    </div>
  `,
  props: ['foo']
}

describe('Foo', () => {
  it('renders bar for foo prop', async () => {
    const wrapper = mount(Foo, {
      propsData: {
        foo: 'bar'
      }
    })
    expect(wrapper.find('p').text()).toContain('bar')
  })
})

The 2nd argument of the mount method takes the propsData property to let us mock the props.

Conclusion

We can mock props and set values of reactive properties in our tests and test them.

Child component events can also be tested from the parent.

Categories
Vue

How to Use Axios with Vuex

Vuex is a state management library for Vue apps.

It’s the most popular choice for state management for Vue apps.

In this article, we’ll look at how to use the Axios HTTP client with Vuex.

Run Async Code with Actions

We can run async code in a Vuex store within actions.

To add an action, we just add the action property into the object we pass into the Vuex.Store constructor.

For example, we can write:

main.js

import Vue from "vue";
import App from "./App.vue";
import Vuex from "vuex";

Vue.config.productionTip = false;
Vue.use(Vuex);

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

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

to add the actions property with the increment method.

The context.commit method commits the mutation we want to change the state.

Then we can dispatch the action in our component with the this.$store.dispatch method:

App.vue

<template>
  <div id="app">
    <button @click="increment">increment</button>
    <p>{{count}}</p>
  </div>
</template>

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

Since actions can have async code in it, we can just add the async code we want in the action method.

The code can update the state.

To use Axios to make a request in our action, we write:

main.js

import Vue from "vue";
import App from "./App.vue";
import Vuex from "vuex";
import axios from "axios";

Vue.config.productionTip = false;
Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    post: {}
  },
  mutations: {
    setPost(state, data) {
      state.post = data;
    }
  },
  actions: {
    async getPost(context, { id }) {
      const { data } = await axios.get(
        `https://jsonplaceholder.typicode.com/posts/${id}`
      );
      context.commit("setPost", data);
    }
  }
});

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

App.vue

<template>
  <div id="app">
    <button @click="getPost">get post</button>
    <p>{{post}}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  computed: {
    post() {
      return this.$store.state.post;
    }
  },
  methods: {
    getPost() {
      this.$store.dispatch("getPost", { id: 1 });
    }
  }
};
</script>

The difference is that we have the getPost async method to make the request to the API.

Then once we have the result, we call context.commit to commit the setPost mutation to update the state.

In the setPost mutation method, we get the data parameter’s value and then set it as the value of state.post to update the post state.

In App.vue , we dispatch the getPost action with the this.$store.dispatch method the same way.

Just that we add an object with the id property to pass it to the getPost action method as the 2nd parameter.

In the post computed property, we return the value of the this.$store.state.post property.

Now when we click the get post button, we should see the post data displayed from the post state via the post computed property.

Conclusion

We can use Axios with Vuex as long as we put the Axios code in the Vuex store action.

An action method can have async code but mutations can’t.