Test Driven Development in VueJS Applications Pt. 1 — Testing Intro

Ricardo Delgado
PENNgineering
5 min readAug 26, 2021

--

Part 1 — Testing Intro

It’s sometimes said that Frontend Developers are as fond of unit tests as vampires are of garlic and crosses.

Test Driven Development (TDD) is the programming methodology that advocates writing tests before code, focuses on solving the minimum requirements of an increasing level of test condition complexity, and forces developers to break down their own thought processes before trying to tackle a solution.

A perfect example of this is a multilayered solution to something like formatting currency or time — something every Frontend Dev has had to deal with all too often.

Unfortunately, we Frontend engineers tend to shy away from adopting TDD because of how it can interfere with the “Wild West” aspect of writing frontend code: namely cool things appearing on the screen with the simple press of cmd+s (something I call the “magical realism of javascript.”)

Common complaints include:

  1. It slows me down
  2. My code isn’t that complicated anyway
  3. It breaks too much and I have to refactor all the time
  4. It’s too complicated for not much gain.

While many of these complaints feel like they’re valid, I would argue that (1) TDD makes development faster; (2) TDD makes code very clean; (3) TDD catches errors on an already-fragile language; and (4) TDD can make you a much, much better programmer in general.

Testing In VueJS

Thankfully, VueJS has a pretty solid testing library — https://vue-test-utils.vuejs.org/ currently maintained by the esteemed Lachlan Miller, and previously by Edd Yerburgh.

Two things to note: this article will be using Jest for our unit tests, and also we will only focus on Vue2, even though at Penn Interactive we‘ve already transitioned to Vue3 via Vue’s composition-api plugin.

While many of the principle’s we’ll be covering apply to Vue3, there are some nitpicky issues to testing Vue3 like dealing with computed .value nuances that don’t really matter much, so I won’t be addressing it within this article.

Step 1: Breaking Away From UI

In my experience, one of the harder mental shifts a developer needs to make when embarking on TDD is to break away from the “I push a button then something appears” mentality.

I would categorize this more as an “end-to-end” (e2e) testing mentality, and gets in the way of unit tests because the developer is thinking too literally.

The beauty of unit tests are their confinement to units, or as I like to call it, “the smallest steps possible between code.”

e2e testing gets lost in, “I push a button then something appears,” without accounting for all the steps between those two happenings.

The unit tester comes from the angle that it doesn’t matter what calls a function or method (unless there is a conditional), but rather only what it does with the inputs it’s given.

This also tiptoes into functional programming, but that a topic for another day.

Step 2: Setting up tests in VueJS

Most VueJS tests will center around the two main types of files, Single File Components (SFC) and Vuex Store Modules. There’s also a third type to round things out, but that will mostly cover helper and abstracted functions, and should be handled like regular javascript files.

SFCs are different because it requires hooking into the Vue ecosystem, creating an isolated mounted component and testing it through Vue Test Utils. Vuex Store Modules test more closely to javascript files, but can be finicky if not treated properly.

The base SFC test file will look something like:

// Import shallowMount. createLocalVue is only required if using Vuex or using Plugin in test.
import { shallowMount, createLocalVue } from '@vue/test-utils'
// Import Vuex (optional)
import Vuex from 'vuex'
// Import Component
import Test from '@/layouts/components/Test.vue'
// Only required if using Vuex or local Plugin
const localVue = createLocalVue() 10localVue.use(Vuex)
// Set up test 13describe('@/layouts/components/Test.vue', () => {
let wrapper;
// declare any Vuex state, getters, or actions separately
// so that their values can be changed for testing purposes
let subModulesState;
beforeEach(() => {
// Load component and import requirements
// store & localVue only required if using Vuex
const wrapper = shallowMount(Test, {
store: new Vuex.Store({
namespaced: true,
state: {
user: {
first_name: 'John',
last_name: 'Doe'
},
modules: {
subModule: {
namespaced: true // add namespacing for any module
state: subModuleState
}
}
}),
localVue,
// Add props (optional)
propsData: {
prop1: 'value'
}
})
})
// Test top level element to ensure base render
test('sanity check', () => {
expect(wrapper.find('.{parent dom element class}').exists()).toBe(true)
})
// leverage describe blocks for organization
describe('computed properties', () => {
test('property 1', () => {}
})
describe('methods', () => {
test('method 1', () => {}
})

One thing to note, shallowMount vs mount. In the past I was an advocate of never needing to use shallowMount but there are definitely cases in which mount should be used, and mostly it’s around the use of props.

I would still advocate always deferring to shallowMount , but definitely an opinion open for debate.

The basic test file for a Vuex Store Module:

// Import the entire store file as reassign a different variable
import * as testStore from '@/store/test'
import { cloneDeep } from 'lodash'
describe('@/store/test', () => {
let state,
getters,
actions,
mutations,
commit
// clone the store file to create clean environment
// and prevent side effects
const cloneStore = cloneDeep(testStore)
beforeEach(() => {
// assign store, getters, actions,
// and mutations to local variables
state = cloneStore.state()
getters = cloneStore.getters
actions = cloneStore.actions
mutations = cloneStore.mutations
commit = jest.fn()
}

In the next post, I’ll get into the details of how to write VueJS code via the test first (TDD) method, and how to apply that to Vue SFCs and Store Module files.

About the Author

Ricardo Delgado is a self-taught VueJS Fullstack Web Developer with a Frontend Bias. He is the creator of Anti-Clever Development for VueJS Applications, and an advocate of Test Driven Development, Component Driven Design, and Domain Driven Design.

Twitter: https://twitter.com/ricardod_dev
Github: https://github.com/rdelga80
Portfolio: https://rdelgado-portfolio.web.app/

--

--