Search code examples
vue.jsvue-test-utils

Vue Test Utils - Unable to correctly mount/shallow mount component, wrapper undefined


I've tried almost everything I can think of but I'm unable to correctly mount/shallow mount my vue components for testing correctly. Everytime I console.log the wrapper I get the following print out:

VueWrapper {
  isFunctionalComponent: undefined,
  _emitted: [Object: null prototype] {},
  _emittedByOrder: []
}

This question is similar to this question asked here:

Vue-test-utils wrapper undefined

I'm using Vuetify, Vuex and Vue Router. My test.spec.ts is below:

import { shallowMount, createLocalVue, mount } from "@vue/test-utils"
import Vuex from "vuex"
import Vuetify from "vuetify"
import VueRouter from "vue-router"
import TheExamAnswer from "@/components/common/TheExamAnswer.vue"

describe("TheExamAnswer.vue", () => {
  const localVue = createLocalVue()
  let getters: any
  let store: any
  let vuetify: any
  let router: any

  beforeEach(() => {
    localVue.use(Vuex)
    localVue.use(Vuetify)
    localVue.use(VueRouter)
    getters = {
      getExam: () => true,
    }

    store = new Vuex.Store({
      modules: {
        // Need to add FlightPlanning for name spacing
        FlightPlanning: {
          namespaced: true,
          getters,
        },
      },
    })

    vuetify = new Vuetify()

    router = new VueRouter()
  })

  it("Renders the element if the exam has been submitted", () => {
    const wrapper = mount(TheExamAnswer, { localVue, store, router })
    console.log("This is the HTML", wrapper.html())
    expect(wrapper.text()).toContain("Show Answer")
  })
})

My view component is very simple and the code is below:

<template>
  <div v-if="submitted" class="div">
    <v-btn @click="answerHidden = !answerHidden" class="mb-10"
      >Show Answer</v-btn
    >
    <div v-if="!answerHidden">
      <slot name="questionAnswer"></slot>
    </div>
  </div>
</template>

<script>
export default {
  data: () => {
    return {
      answerHidden: true,
    }
  },
  computed: {
    submitted() {
      const exam = this.$store.getters["FlightPlanning/getExam"]
      return exam.submitted
    },
  },
}
</script>

<style></style>

UPDATED: I've added the suggestion from the answer below however now I"m getting the following message.

 TheExamAnswer.vue
    ✕ Renders the element if the exam has been submitted (49ms)

  ● TheExamAnswer.vue › Renders the element if the exam has been submitted

    expect(received).toContain(expected) // indexOf

    Expected substring: "Show Answer"
    Received string:    ""

      38 |     const wrapper = mount(TheExamAnswer, { localVue, store, router })
      39 |     console.log("This is the HTML", wrapper.html())
    > 40 |     expect(wrapper.text()).toContain("Show Answer")
         |                            ^
      41 |   })
      42 | })
      43 | 

      at Object.it (tests/unit/test.spec.ts:40:28)

  console.error node_modules/vuetify/dist/vuetify.js:43612
    [Vuetify] Multiple instances of Vue detected
    See https://github.com/vuetifyjs/vuetify/issues/4068
    
    If you're seeing "$attrs is readonly", it's caused by this

  console.log tests/unit/test.spec.ts:39
    This is the HTML 

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total

As you can see the HTML is blank and therefore I'm presuming that's also the same reason it's failing this test as the received string is "".

SOLUTION -

I figured it out. The error was on my behalf by not looking at the logic of the computed property correctly.

In my test I had:

 getters = {
          getExam: () => true,
        }

In my component I had:

computed: {
    submitted() {
      const exam = this.$store.getters["FlightPlanning/getExam"]
      return exam.submitted
    },

If you look at the logic of the computed property it going to take whats returned from the getter and assign it to the exam variable. Originally I was returning true, because that's what I wanted the submitted() function to return this means when I call exam.submitted I'm calling it on a boolean value which obviously gives me "undefined". The solution was to return exactly what the computed property was designed to deal with, an object i.e. {submitted:true}

Therefore the final test looks like this and is returning valid HTML.

import { shallowMount, createLocalVue, mount } from "@vue/test-utils"
import Vuex from "vuex"
import Vuetify from "vuetify"
import VueRouter from "vue-router"
import TheExamAnswer from "@/components/common/TheExamAnswer.vue"

const localVue = createLocalVue()

localVue.use(Vuex)
localVue.use(Vuetify)
localVue.use(VueRouter)

describe("test.vue", () => {
  let getters: any
  let store: any
  let vuetify: any
  let router: any

  beforeEach(() => {
    getters = {
      getExam: () => {
        return { submitted: true }
      },
    }

    store = new Vuex.Store({
      modules: {
        // Need to add FlightPlanning for name spacing
        FlightPlanning: {
          namespaced: true,
          getters,
        },
      },
    })

    vuetify = new Vuetify()

    router = new VueRouter()
  })

  it("Renders the element if the exam has been submitted", () => {
    const wrapper = mount(TheExamAnswer, { localVue, vuetify, store, router })
    console.log("This is the HTML", wrapper.html())
  })
})

This gives me the result of:

  console.log tests/unit/test.spec.ts:44
    This is the HTML <div><button type="button" class="mb-10 v-btn v-btn--contained theme--light v-size--default"><span class="v-btn__content">Show Answer</span></button>
      <!---->
    </div>

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        5.235s
Ran all test suites.

Solution

  • The console.log with that weird input for wrapper or elements is normal behaviour. I ran couple tests with your components and everything was working.

    it("Renders the element if the exam has been submitted", () => {
      const wrapper = mount(TheExamAnswer, { localVue, store, router });
      expect(wrapper.text()).toContain("Show Answer");
    });
    

    If you want to console.log html in your wrapper:

    console.log(wrapper.html())
    

    UPDATED: the reason, why wrapper.html() return empty string is v-if="submitted" on your root component. The computed property return undefined, because getter return true, so true.submitted return undefined

    Getter in test.spec.ts:

    getters = {
      getExam: () => {
        return { submitted: true };
      }
    };