This book is written for Vue.js 3 and Vue Test Utils v2.

Find the Vue.js 2 version here.

Testing Computed Properties

You can find the test described on this page hereComputed Properties - 图1.

Testing computed properties are especially simple, since they are just plain old JavaScript functions.

Let’s start with looking at two different ways to test a computed property. We will develop a <NumberRenderer> component, that renders either odd or even numbers, based on a numbers computed property.

Writing the test

The <NumberRenderer> component will receive an even prop, that is a boolean. If even is true, the component should render 2, 4, 6, and 8. If false, it should render 1, 3, 5, 7 and 9. The list of values will be calculated in a computed property called numbers.

Testing by rendering the value

The test:

  1. import { mount } from "@vue/test-utils"
  2. import NumberRenderer from "@/components/NumberRenderer.vue"
  3. describe("NumberRenderer", () => {
  4. it("renders even numbers", () => {
  5. const wrapper = mount(NumberRenderer, {
  6. props: {
  7. even: true
  8. }
  9. })
  10. expect(wrapper.text()).toBe("2, 4, 6, 8")
  11. })
  12. })

Before running the test, let’s set up <NumberRenderer>:

  1. <template>
  2. <div>
  3. </div>
  4. </template>
  5. <script>
  6. export default {
  7. name: "NumberRenderer",
  8. props: {
  9. even: {
  10. type: Boolean,
  11. required: true
  12. }
  13. }
  14. }
  15. </script>

Now we start development, and let the error messages guide our implementation. yarn test:unit yields:

  1. NumberRenderer renders even numbers
  2. expect(received).toBe(expected) // Object.is equality
  3. Expected: "2, 4, 6, 8"
  4. Received: ""

It looks like everything is hooked up correctly. Let’s start implementing numbers:

  1. computed: {
  2. numbers() {
  3. const evens = []
  4. for (let i = 1; i < 10; i++) {
  5. if (i % 2 === 0) {
  6. evens.push(i)
  7. }
  8. }
  9. return evens
  10. }
  11. }

And update the template to use the new computed property:

  1. <template>
  2. <div>
  3. {{ numbers }}
  4. </div>
  5. </template>

yarn test:unit now yields:

  1. FAIL tests/unit/NumberRenderer.spec.js
  2. NumberRenderer renders even numbers
  3. expect(received).toBe(expected) // Object.is equality
  4. Expected: "2, 4, 6, 8"
  5. Received: "[
  6. 2,
  7. 4,
  8. 6,
  9. 8
  10. ]"

The numbers are correct, but we want to render the list formatted nicely. Let’s update the return value:

  1. return evens.join(", ")

Now yarn test:unit passes!

Testing with call

We will now add a test for the case of even: false. This time, we will see an alternative way to test a computed property, without actually rendering the component.

The test, first:

  1. it("renders odd numbers", () => {
  2. const localThis = { even: false }
  3. expect(NumberRenderer.computed.numbers.call(localThis)).toBe("1, 3, 5, 7, 9")
  4. })

Instead of rendering the component and making an assertion on wrapper.text(), we are using call to provide alternative this context to numbers. We will see what happens if we don’t use call after we get the test to pass.

Running the current test yields:

  1. FAIL tests/unit/NumberRenderer.spec.js
  2. NumberRenderer renders odd numbers
  3. expect(received).toBe(expected) // Object.is equality
  4. Expected: "1, 3, 5, 7, 9"
  5. Received: "2, 4, 6, 8"

Update numbers:

  1. numbers() {
  2. const evens = []
  3. const odds = []
  4. for (let i = 1; i < 10; i++) {
  5. if (i % 2 === 0) {
  6. evens.push(i)
  7. } else {
  8. odds.push(i)
  9. }
  10. }
  11. return this.even === true ? evens.join(", ") : odds.join(", ")
  12. }

Now both tests pass! But what if we hadn’t used call in the second test? Try updating it like so:

  1. it("renders odd numbers", () => {
  2. const localThis = { even: false }
  3. expect(NumberRenderer.computed.numbers()).toBe("1, 3, 5, 7, 9")
  4. })

The test now fails:

  1. FAIL tests/unit/NumberRenderer.spec.js
  2. NumberRenderer renders odd numbers
  3. expect(received).toBe(expected) // Object.is equality
  4. Expected: "1, 3, 5, 7, 9"
  5. Received: "2, 4, 6, 8"

vue automatically binds props to this. We are not rendering the component with mount, though, so Vue isn’t binding anything to this. If you do console.log(this), you can see the context is simply the computed object:

  1. { numbers: [Function: numbers] }

So we need to use call, which lets us bind an alternative this object, in our case, one with a even property.

To call or to mount?

Both techniques presented are useful for testing computed properties. Call can be useful when:

  • You are testing a component that does some time consuming operations in a lifecycle methods you would like to avoid executing in your computed unit test.
  • You want to stub out some values on this. Using call and passing a custom context can be useful.

Of course, you want to make sure the value is correctly rendered as well, so make sure you choose the correct technique when testing your computed properties, and test all the edge cases.

Conclusion

  • computed properties can be using mount making assertions on the rendered markup
  • complex computed properties can be independently tested by using call