All Projects → dekadentno → vue-unit-testing-cheat-sheet

dekadentno / vue-unit-testing-cheat-sheet

Licence: Apache-2.0 license
🔬 My cheat sheet for testing vue components with jest and vue-test-utils

Programming Languages

Vue
7211 projects

Projects that are alternatives of or similar to vue-unit-testing-cheat-sheet

react-cheatsheets
Create and generate cheat sheets using React
Stars: ✭ 21 (-79.21%)
Mutual labels:  cheatsheet, cheat, sheet
Arduino-Cheat-Sheet
Cheat Sheets for Arduino Programming Language
Stars: ✭ 30 (-70.3%)
Mutual labels:  cheatsheet, cheat, sheet
C Sharp Cheatsheet
C# Cheatsheet
Stars: ✭ 111 (+9.9%)
Mutual labels:  cheatsheet, cheat
Huge Collection Of Cheatsheet
Share of my Huge Collection of Cheatsheet (Coding, Cheat, Pinouts, Command Lists, Etc.)
Stars: ✭ 250 (+147.52%)
Mutual labels:  cheatsheet, cheat
Javascript Cheatsheet
Basic Javascript Cheat Sheet
Stars: ✭ 262 (+159.41%)
Mutual labels:  cheatsheet, cheat
Vue Cheat Sheet
📚 My cheat sheet for vue.js most basic stuff https://boussadjra.github.io/vue-cheat-sheet/
Stars: ✭ 90 (-10.89%)
Mutual labels:  cheatsheet, cheat
Active Directory Exploitation Cheat Sheet
A cheat sheet that contains common enumeration and attack methods for Windows Active Directory.
Stars: ✭ 1,392 (+1278.22%)
Mutual labels:  cheatsheet, cheat
Pyspark Cheatsheet
🐍 Quick reference guide to common patterns & functions in PySpark.
Stars: ✭ 108 (+6.93%)
Mutual labels:  cheatsheet, cheat
Python Cheatsheet
Collection of Python code snippets and cheatsheets (made for humans)
Stars: ✭ 176 (+74.26%)
Mutual labels:  cheatsheet, cheat
Angular Awesome List
Список ресурсов по Angular на русском
Stars: ✭ 224 (+121.78%)
Mutual labels:  cheatsheet
Command Mobile Penetration Testing Cheatsheet
Mobile penetration testing android & iOS command cheatsheet
Stars: ✭ 221 (+118.81%)
Mutual labels:  cheatsheet
Rust Cheatsheet
A type-based Rust cheatsheet
Stars: ✭ 220 (+117.82%)
Mutual labels:  cheatsheet
Sinon Jest Cheatsheet
Some examples on how to achieve the same goal with either of both libraries: sinon and jest. Also some of those goals achievable only by one of these tools.
Stars: ✭ 226 (+123.76%)
Mutual labels:  cheatsheet
Bootstrap4 Cheatsheet
Bootstrap 4 Cheat Sheet
Stars: ✭ 254 (+151.49%)
Mutual labels:  cheatsheet
JDuel-Links-Bot
An all-in-one modding tool and bot for Yu-Gi-Oh! Duel Links (Steam version)
Stars: ✭ 25 (-75.25%)
Mutual labels:  cheat
R Web Scraping Cheat Sheet
Guide, reference and cheatsheet on web scraping using rvest, httr and Rselenium.
Stars: ✭ 207 (+104.95%)
Mutual labels:  cheatsheet
Es6 All The Things
Collection of ES6 goodies for next-level dev 🤓💻
Stars: ✭ 206 (+103.96%)
Mutual labels:  cheatsheet
docker-cheat-sheet
Important Docker Commands Ordered by Compute (Services), Network, Storage then by Docker CLI, Dockerfile, Compose, and Swarm
Stars: ✭ 21 (-79.21%)
Mutual labels:  cheatsheet
ruuand.github.io
Wiki for stuff
Stars: ✭ 30 (-70.3%)
Mutual labels:  cheatsheet
Stanford Cme 106 Probability And Statistics
VIP cheatsheets for Stanford's CME 106 Probability and Statistics for Engineers
Stars: ✭ 242 (+139.6%)
Mutual labels:  cheatsheet

Vue unit testing cheat sheet

🔬 My cheat sheet for testing vue components with jest and vue test utils

Remark: In the time of making this cheat sheet, I had no clue about (unit) testing, so these examples might not be in terms of good/best practices.

🙏 Many examples in here are taken from the repos, videos and tutorials from MikaelEdebro, Edd Yerburgh, Alex Jover and others.

Useful links:

A few words before

Where is the right balance between what to test and what not to test? We can consider writing unit tests in cases like:

  • when the logic behind the method is complex enough that you feel you need to test extensively to verify that it works.
  • whenever it takes less time to write a unit test to verify that code works than to start up the system, log in, recreate your scenario, etc.
  • when there are possible edge cases of unusually complex code that you think will probably have errors
  • when a particulary complex function is receiving multiple arguments, it is a good idea to feed that function with null, undefined and unexpected data (e.g. methodNullTest, methodInvalidValueTest, methodValidValueTest)
  • when there are cases that require complex steps for reproduction and can be easily forgotten

It's also important to say that we have to tests are inputs and outputs, and NOT the logic between them. Meaning, for example, if we are testing a component that will give us a random number as a result, where we will specify the minimum and maximum number, our inputs will be the lowest number and the highest number (the range) and the output will be our random number. We are not interested in the steps how that result is calculated, just the inputs and outpust. That gives us the flexibility to change or optimize the logic, but the tests should not fail if we do that.

Also, we shouldn't test the framework we are using not the 3rd party libraries. We have to assume they are tested.

Try to keep test methods short and sweet and add them to the build.

Setuping jest

To avoid this chapter, if you are just starting a project or learning, just scafold a new vue project with vue create and while manually selecting features needed for your project, pick unit testing and Jest as your testing framework of choice. Follow these steps for the most basic jest setup:

  1. npm i --save-dev jest

  2. add "unit": "jest" to your package.json file

    {
      "scripts": {
        "unit": "jest",
      },
    } 
  3. create Component.spec.js file in the component folder

  4. add jest: true to your .eslintrc.js file (so that eslint knows the jest keywords like describe, expect etc.)

    {
      env: {
        browser: true,
        jest: true
      },
    }
  5. Write tests in the Component.spec.js file and run it with npm run unit

Things you'll almost always use

  • (1) use mount to mount the component and store it in wrapper
  • (2) access data in a component
  • (3) change a variable in data in a component
  • (4) find an element in a component and check it's properties
  • (5) trigger events like click on an element
  • (6) check if a component has an html element
  • (7) manually pass props to a component

Frequent terminology

  • Shallow Rendering - a technique that assures your component is rendering without children. This is useful for:
    • Testing only the component you want to test (that's what Unit Test stands for)
    • Avoid side effects that children components can have, such as making HTTP calls, calling store actions...

Sanity test

Sanity tests will obviously always have to pass. We are writing them to see that if they somehow fail, we probably didn't set up something right.

describe('Component.vue', () => {
  test('sanity test', () => {
    expect(true).toBe(true) // will obviously always pass
  })
})

Access Vue component

// component access
import { mount } from '@vue/test-utils'
import Modal from '../Modal.vue'
const wrapper = mount(Modal)

wrapper.vm // access to the component instance
wrapper.element // access to the component DOM node

Test Vue components

// Import Vue and the component being tested
import Vue from 'vue'
import MyComponent from 'path/to/MyComponent.vue'

describe('MyComponent', () => {
  // Inspect the raw component options
  it('has a created hook', () => {
    expect(typeof MyComponent.created).toBe('function')
  })

  // access the component data 
  it('check init state of "message" from data', () => {
    const vm = new Vue(MyComponent).$mount()
    expect(vm.message).toBe('bla')
  })
  
  // usage of helper functions 
  it('hides the body initially', () => {
    h.domHasNot('body')
  })
  it('shows body when clicking title', () => {
    h.click('title')
    h.domHas('.body')
  })
  it('renders the correct title and subtitle after clicking button', () => {
    h.click('.my-button')
    h.see('My title')
    h.see('My subtitle')
  })
  it('adds expanded class to expanded post', () => {
    h.click('.post')
    expect(wrapper.classes()).toContain('expanded')
  })
  it('show comments when expanded', () => {
    h.click('.post')
    h.domHas('.comment')
  })
})

Test default and passed props

// we'll create a helper factory function to create a message component, give some properties
const createCmp = propsData => mount(KaModal, { propsData });

it("has a message property", () => {
  cmp = createCmp({ message: "hey" });
  expect(cmp.props().message).toBe("hey");
});

it("has no cat property", () => {
  cmp = createCmp({ cat: "hey" });
  expect(cmp.props().cat).toBeUndefined();
});

// test default value of author prop
it("Paco is the default author", () => {
  cmp = createCmp({ message: "hey" });
  expect(cmp.props().author).toBe("Paco");
});

// slightly different approach
// pass props to child with 'propsData'
it('renders correctly with different props', () => {
  const Constructor = Vue.extend(MyComponent)
  const vm = new Constructor({ propsData: {msg: 'Hello'} }).$mount()
  expect(vm.$el.textContent).toBe('Hello')
})

// prop type
it("hasClose is bool type", () => {
    const message = createCmp({title: "hey"});
    expect(message.vm.$options.props.hasClose.type).toBe(Boolean);
})

// prop required
it("hasClose is not required", () => {
    const message = createCmp({title: "hey"});
    expect(message.vm.$options.props.hasClose.required).toBeFalsy();
})

// custom events
it("Calls handleMessageClick when @message-click happens", () => {
  const stub = jest.fn();
  cmp.setMethods({ handleMessageClick: stub });
  const el = cmp.find(Message).vm.$emit("message-clicked", "cat");
  expect(stub).toBeCalledWith("cat");
});

Test visibility of component with passed prop

test('does not render when not passed visible prop', () => {
  const wrapper = mount(Modal)
  expect(wrapper.isEmpty()).toBe(true)
})
test('render when visibility prop is true', () => {
  const wrapper = mount(Modal, {
    propsData: {
      visible: true
    }
  })
  expect(wrapper.isEmpty()).toBe(false)
})
test('call close() method when X is clicked', () => {
  const close = jest.fn()
  const wrapper = mount(Modal, {
    propsData: {
      visible: true,
      close
    }
  })
  wrapper.find('button').trigger('click')
  expect(close).toHaveBeenCalled()
})

Test computed properties

it("returns the string in normal order", () => {
  cmp.setData({ inputValue: "Yoo" });
  expect(cmp.vm.reversedInput).toBe("Yoo");
});

Vuex actions

Before testing anything from the vuex store, we need to "mock" (make dummy / hardcode) the store values that we want to test. In the case beneath, we simulated the result of the getComments async action to give us 2 comments. Read more https://lmiller1990.github.io/vue-testing-handbook/vuex-actions.html#creating-the-action

import Vuex from 'vuex'
import { shallow, createLocalVue } from '@vue/test-utils'
import BlogComments from '@/components/blog/BlogComments'
import TestHelpers from 'test/test-helpers'
import Loader from '@/components/Loader'
import flushPromises from 'flush-promises'

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

describe('BlogComments', () => {
  let wrapper
  let store
  // eslint-disable-next-line
  let h
  let actions
  beforeEach(() => {
    actions = {
      getComments: jest.fn(() => {
        return new Promise(resolve => {
          process.nextTick(() => {
            resolve([{ title: 'title 1' }, { title: 'title 2' }])
          })
        })
      })
    }
    store = new Vuex.Store({
      modules: {
        blog: {
          namespaced: true,
          actions
        }
      }
    })
    wrapper = shallow(BlogComments, {
      localVue,
      store,
      propsData: {
        id: 1
      },
      stubs: {
        Loader
      },
      mocks: {
        $texts: {
          noComments: 'No comments'
        }
      }
    })
    h = new TestHelpers(wrapper, expect)
  })

  it('renders without errors', () => {
    expect(wrapper.isVueInstance()).toBeTruthy()
  })

  it('calls action to get comments on mount', () => {
    expect(actions.getComments).toHaveBeenCalled()
  })

  it('shows loader initially, and hides it when comments have been loaded', async () => {
    h.domHas(Loader)
    await flushPromises()
    h.domHasNot(Loader)
  })

  it('has list of comments', async () => {
    await flushPromises()
    const comments = wrapper.findAll('.comment')
    expect(comments.length).toBe(2)
  })

  it('shows message if there are no comments', async () => {
    await flushPromises()
    wrapper.setData({
      comments: []
    })
    h.domHas('.no-comments')
    h.see('No comments')
  })
})

Vuex mutations and getters

Don't forget to correctly export mutations and getters so they can be accessible in the tests

import { getters, mutations } from '@/store/modules/blog'

describe('blog store module', () => {
  let state
  beforeEach(() => {
    state = {
      blogPosts: []
    }
  })
  describe('getters', () => {
    it('hasBlogPosts logic works', () => {
      expect(getters.hasBlogPosts(state)).toBe(false)
      state.blogPosts = [{}, {}]
      expect(getters.hasBlogPosts(state)).toBe(true)
    })
    it('numberOfPosts returns correct count', () => {
      expect(getters.numberOfPosts(state)).toBe(0)
      state.blogPosts = [{}, {}]
      expect(getters.numberOfPosts(state)).toBe(2)
    })
  })

  describe('mutations', () => {
    it('adds blog posts correctly', () => {
      mutations.saveBlogPosts(state, [{ title: 'New post' }])
      expect(state.blogPosts).toEqual([{ title: 'New post' }])
    })
  })
})

Snapshot test

From the official jest documentation:

Snapshot tests are a very useful tool whenever you want to make sure your UI does not change unexpectedly. A typical snapshot test case for a mobile app renders a UI component, takes a screenshot, then compares it to a reference image stored alongside the test. The test will fail if the two images do not match: either the change is unexpected, or the screenshot needs to be updated to the new version of the UI component.

You can use this plugin to improve and better control the formatting of your snapshots:

test('Modal renders correctly when visible: true', () => {
  const wrapper = mount(Modal, {
    propsData: {
      visible: true
    }
  });

  // Will store a snapshot of the entire component
  expect(wrapper)
    .toMatchSnapshot();
});
test('Advanced form is shown after clicking "show more"', async () => {
  const wrapper = mount(GenericForm);

  // Target a specific element and similuate interacting with it
  const showMore = wrapper.find('[data-test="showMore"]');
  showMore.trigger('click');
  
  // Wait for the trigger event to be handled
  await wrapper.vm.$nextTick();

  // Will store a snapshot of a portion of the component containing
  // the element that has a matching data-test attribute.
  expect(wrapper.find('[data-test="advancedForm"]'))
    .toMatchSnapshot();
});

Testing helper functions

import { sort } from './helpers'

describe('Helper functions', () => {
  test('it sorts the array', () => {
    const arr = [3,1,5,4,2]
    const res = sort(arr, 'asc')
    expect(res).toEqual(arr.sort((a,b) => a-b))
  })
})

Useful helpers

class TestHelpers {
  constructor(wrapper, expect) {
    this.wrapper = wrapper
    this.expect = expect
  }

  see(text, selector) {
    let wrap = selector ? this.wrapper.find(selector) : this.wrapper
    this.expect(wrap.html()).toContain(text)
  }
  doNotSee(text) {
    this.expect(this.wrapper.html()).not.toContain(text)
  }
  type(text, input) {
    let node = this.find(input)
    node.element.value = text
    node.trigger('input')
  }
  click(selector) {
    this.wrapper.find(selector).trigger('click')
  }
  inputValueIs(text, selector) {
    this.expect(this.find(selector).element.value).toBe(text)
  }
  inputValueIsNot(text, selector) {
    this.expect(this.find(selector).element.value).not.toBe(text)
  }
  domHas(selector) {
    this.expect(this.wrapper.contains(selector)).toBe(true)
  }
  domHasNot(selector) {
    this.expect(this.wrapper.contains(selector)).toBe(false)
  }
  domHasLength(selector, length) {
    this.expect(this.wrapper.findAll(selector).length).toBe(length)
  }
  isVisible(selector) {
    this.expect(this.find(selector).element.style.display).not.toEqual('none')
  }
  isHidden(selector) {
    this.expect(this.find(selector).element.style.display).toEqual('none')
  }
  find(selector) {
    return this.wrapper.find(selector)
  }
  hasAttribute(selector, attribute) {
    return this.expect(this.find(selector).attributes()[attribute]).toBeTruthy()
  }
}

export default TestHelpers
// how to use helpers
import TestHelpers from 'test/test-helpers'
let h = new TestHelpers(wrapper, expect)
h.domHas('.loader')
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].