Test Driven Development in VueJS Applications Pt. 2— Methods and Computed Properties

Ricardo Delgado
PENNgineering
6 min readSep 29, 2021

--

Previously, I introduced testing in VueJS applications, their importance and how to setup the testing files.

This post will delve more into the nitty gritty on what exactly will be tested in VueJS, and how to do testing on two fundamental elements of the VueJS Options API: Methods and Computed Properties.

Computed Properties: https://vuejs.org/v2/guide/computed.html
Methods: https://vuejs.org/v2/guide/instance.html#Data-and-Methods

Writing Testable Code

Understanding the concept of “testable code” will help as a programmer in two ways: 1. it will make it easier for you to write tests, and 2. it will help force your code to be clear and concise in ways that will improve general code quality.

Testable Code just means breaking your code down in a way that makes it easy to digest as a unit. Hence the term “unit testing.”

Without testing, it’s easy to write code that is trying to do too much. It could be a function wrapping a function, assigning it to a variable, mutating it via a reduce or map, then using a find to get the value.

const confusingCode = () => {
return axios.get('something')
.then(({ data }) => {
const reducedData = data.reduce((acc, i) => { ... })
const mappedData = reducedData.map(j => { ... }) const foundItem = mappedData.find(item => { ... }) return foundItem
})
}

This code works and will return the item you desired to be found, but it is basically impossible to test this, and requires your mocked data to be overly specific — a general no-no in unit testing, which prefers mocks and data to be as simple as possible.

Writing testable code means thinking about your code in steps and breaking those steps down so that you can peek into its functionality and make sure it’s doing exactly what’s desired.

For the test above, each step should broken out into it’s own function and handle just one aspect of the function, rather than one function doing too many things.

Meaning the map, reduces, and finds, could all be abstracted as their own functions, then have their own tests.

Another example of not writing testable code is not adhering to functional programming or pure functions:

const timeDifference = () => {
const timeNow = Date.now()
const endOfYear = new Date('1999-12-31')
return timeNow - endOfYear
}

While this simple function seems easy enough to test, it’s too reliant on knowledge not pertinent to the function, and therefore can have unexpected side effects.

The only way to test this function is to basically rewrite it, or hardcode the expected response, then test against that — this may not actually test properly because of the differences that occur because computer time, the time of the testing suite, or even worse if another dev lives in a different timezone.

A more testable way to write this function would be:

const timeDifference = (dateStart, dateEnd) => {
return dateEnd - dateStart
}

Testing Computed Properties

In VueJS a computed property is basically a reactive version of a data item.

Here’s a basic example of testing a computed property, that can be tested two different ways:

// Vue File
<script>
data() {
return {
dataItem: 'test',
}
}
computed: {
upperCaseDataItem() {
return this.dataItem.toUpperCase()
}
}
}

One way to test it is directly through the ViewModel (vm):

// Test File
describe('computed properties', () => {
describe('upperCaseDataItem', () => {
it('returns dataItem as upperCase', () => {
expect(wrapper.vm.upperCaseDataItem).toBe('TEST')
}
})
})

Another way is to test against the function itself in the Vue File:

// Test File
import FileToTest from 'path/to/url/FileToTest.vue'
describe('computed properties', () => {
describe('upperCaseDataItem', () => {
it('returns dataItem as upperCase', () => {
const localThis = {
dataItem: 'localThis data'
}
const upperComputedProperty = FileToTest.options.computed.upperCaseDataItem expect(upperComputedProperty.call(localThis)).toBe('LOCALTHIS DATA')
}
})
})

By using call you can more directly test how the computed property reads data fed into it via this . This is especially useful if the computed property is reading data items from Vuex, or data that may or may not be present based on specific conditions — meaning, rather than triggering the condition, the data is directly adjusted via localThis.

Some issues one should keep an eye out for in computed properties is trying to call methods/functions, or trying to assign values. The Vue Way would push one to only do these sorts of actions within a method, and could be a sign that refactoring needs to happen.

Testing Methods

Methods, in a way, are easier to test than computed properties since they follow more closely with testing normal functions and they can be tested with less reliance on knowing about the Vue ecosystem.

Methods can be tricky because of the previously discussing issues of writing testable code. There could be a very long discussion between two developers of different perspectives for a method written like this:

// Vue File
methods: {
handlerFunction () {
if (this.dataCondition) {
this.dataArray = this.someData.map(...)
else {
this.dataArray = this.somethingElse.reduce(...)
}
}
}

Versus:

// Vue File
methods: {
handlerFunction () {
if (this.dataCondition) {
this.mapFunction()
else {
this.reduceFunction()
}
}
}

The second example here is a very strict interpretation of testable code, the first is a more practical and looser interpretation. This might just come down to your team’s style guide but is a good showing of units.

Example of testing Methods

Methods tend to follow similar patterns, and so I’ll try to do a few examples related to them.

  1. Emits
// Vue Filedata() {
return {
name: 'Name'
}
},
methods: {
handleClickAndEmitName() {
this.$emits('click', name)
}
}

This very simple example is used often in regard to components that wrap inputs. After entering a value, name in this case, something is clicked which emits the entered value.

// Test File
describe('methods', () => {
describe('handleClickAndEmitName', () => {
it('on call emits name value', () => {
// set test value different from default value
wrapper.setData({ name: 'Test Name' })
wrapper.vm.handleClickAndEmitName() expect(wrapper.emitted('click')[0]).toBe('Test Name')
}
})
})

What’s important to note is that this test is divorced from a UI click event. Referring back to Part 1 in this series, this is a good example of separating the UI aspect of testing functions, unless necessary (which is rarely the case).

2. Setting a Data Value

// Vue File
data() {
return {
dataItem: 'test'
}
},
methods: {
setDataItemValue(value) {
this.dataItem = value
}
}

The Test File:

describe('method', () => {
describe('setDataItemValue', () => {
it('set passed value as dataItem', () => {
wrapper.setData({ dataItem: 'not test value' })
wrapper.vm.setDataItemValue('test value') expect(wrapper.vm.dataItem).toBe('test value')
})
})
})

3. Calling another function

// Vue File
methods: {
handleMethod() {
this.anotherMethodToCall()
}
}

This is a bit of a non-practical example, but it makes the point. Here’s the test:

// Test File
describe('methods', () => {
describe('handleMethod', () => {
it('calls anotherMethodToCall', () => {
const anotherMethodToCallSpy = jest.spyOn(wrapper.vm, 'anotherMethodToCall')
wrapper.vm.handleMethod() expect(anotherMethodToCallSpy).toHaveBeenCalledTimes(1)
}
})
})

Negative Tests

Negative tests cover situations that can hopefully catch false positives, or situations where a guard is properly applied.

Going back to a previous example:

// Vue File
data() {
return {
dataItem: 'test'
}
},
methods: {
setDataItemValue(value) {
if (value !== 'bad case') {
this.dataItem = value
}
}
}

You can write a negative test like this:

describe('method', () => {
describe('setDataItemValue', () => {
it('does not set dataItem if value is "bad case', () => {
wrapper.setData({ dataItem: 'not test value' })
wrapper.vm.setDataItemValue('bad case') expect(wrapper.vm.dataItem).not.toBe('bad case')
})
})
})

A negative test may not necessarily be that useful by themselves, but can be very informative to the functionality of the code.

This covers a pretty good chunk of the basics of testing Methods and Computed Properties in VueJS. In the next section I’m going to cover one of the more complicated aspects of VueJS testing: Vuex.

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
LinkedIn: https://www.linkedin.com/in/ricardo-delgado-6ab12a93/
Portfolio: https://rdelgado-portfolio.web.app/

--

--