Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking methods on a Vue instance during TDD

Tags:

I'm learning TDD whilst building my Vue app, and trying to abide by the strict laws of only writing enough production code to satisfy a failing unit test. I am really enjoying this approach, but I have run into a roadblock in regards to adding methods to a Vue instance, and testing that they have been called when the event fires from the element in the template.

I cannot find any suggestions as to how I can mock a Vue method given that if I mock the proxied method, it ends up not being called (I'm using Jest and Vue Test Utils).

I am also using Cypress, so I can fill in this test in e2e, but I would like to be able to cover as much as possible with unit tests.

I own the book "Testing Vue.js Applications" by Edd Yerburgh, but in the section regarding testing component methods, he simply states the following:

Often, components use methods internally. For example, to log to the console when a button is clicked [...] You can think of these as private methods—they aren’t intended to be used outside of the component. Private methods are implementation details, so you don’t write tests for them directly.

This approach obviously does not allow the stricter laws of TDD to be followed, so how do the TDD purists handle this?

ButtonComponent.vue

<template>     <button @click="method">Click me</button> </template>  <script>     export default: {         methods: {             method () {                 // Have I been called?             }         }     } </script> 

ButtonComponent.spec.js

it('will call the method when clicked', () => {     const wrapper = shallowMount(ButtonComponent)     const mockMethod = jest.fn()     wrapper.vm.method = mockMethod      const button = wrapper.find('button')     button.vm.$emit('click')      expect(mockMethod).toHaveBeenCalled()     // Expected mock function to have been called, but it was not called }) 
like image 404
J Dawg Avatar asked Dec 16 '18 04:12

J Dawg


People also ask

How do you mock methods in VUE test utils?

Import the shared module into the component under test and in your tests (make sure to use the same import path in both cases). Call jest. mock() to mock the exported functions of the shared module. Reset the mock in your test suite's beforeEach() .

Which testing framework is best for Vue?

Vue does not have a specific testing framework of its own, but most people are using Jest as their unit testing framework of choice. It has an easy to remember API, flexible assertion mechanics and with a large community behind it, you can find many plugins and solutions to common problems.

How do you mock a function?

There are two ways to mock functions: Either by creating a mock function to use in test code, or writing a manual mock to override a module dependency.

Does VUE test utils use Jest?

If you are using the Vue CLI to build your project, you can use the plugin cli-plugin-unit-jest to run Jest tests. The plugin pulls all required dependencies (including jest), creates a jest.


1 Answers

Solution 1: jest.spyOn(Component.methods, 'METHOD_NAME')

You could use jest.spyOn to mock the component method before mounting:

import MyComponent from '@/components/MyComponent.vue'  describe('MyComponent', () => {   it('click does something', async () => {     const mockMethod = jest.spyOn(MyComponent.methods, 'doSomething')     await shallowMount(MyComponent).find('button').trigger('click')     expect(mockMethod).toHaveBeenCalled()   }) }) 

Solution 2: Move methods into separate file that could be mocked

The official recommendation is to "abstract the hard parts away", and use Jest's various mocking mechanisms to mock the abstracted module invoked by the component under test.

For example, to verify a click-handler is invoked:

  1. Move the click-handler's body into a shared JavaScript file.
  2. Import the shared module into the component under test and in your tests (make sure to use the same import path in both cases).
  3. Call jest.mock() to mock the exported functions of the shared module.
  4. Reset the mock in your test suite's beforeEach(). This might only be necessary when there are multiple tests in the suite.
// @/components/MyComponent/utils.js export function doSomething() { /*...*/ } //1️⃣  // @/components/MyComponent/MyComponent.vue (<script>) import { doSomething } from '@/components/MyComponent/utils' //2️⃣  export default {   methods: {     onClick() {       doSomething() //1️⃣     }   } }  // @/test/MyComponent.spec.js import { doSomething } from '@/components/MyComponent/utils' //2️⃣ jest.mock('@/components/MyComponent/utils') //3️⃣  describe('MyComponent', () => {   beforeEach(() => doSomething.mockClear()) //4️⃣    it('click does something', async () => {     await shallowMount(MyComponent).find('button').trigger('click')     expect(doSomething).toHaveBeenCalled()   }) }) 

Solution 3: setMethods() (pre v1.0)

Use setMethods() (deprecated as of v1.0) to overwrite a component method:

describe('MyComponent', () => {   it('click does something', async () => {     // Option A:     const mockMethod = jest.fn()     const wrapper = shallowMount(MyComponent)     wrapper.setMethods({ doSomething: mockMethod })     await wrapper.find('button').trigger('click')     expect(mockMethod).toHaveBeenCalled()      // Option B:     const mockMethod = jest.fn()     const wrapper = shallowMount(MyComponent, {       methods: {         doSomething: mockMethod       }     })     await wrapper.find('button').trigger('click')     expect(mockMethod).toHaveBeenCalled()   }) }) 

demo

like image 163
tony19 Avatar answered Nov 11 '22 03:11

tony19