2

I have a Vue2 project with Vuetify, and i am using Jest for unit testing my code. I am starting out testing some sample code and i simple cannot get Jest to determine if a Vuetify v-alert component is visible or not. I have tried the built in Jest methods as well as adding Jest-dom and using the toBeVisible() method and nothing is working so far.

If you look at the Test.vue component, the v-alert component is hidden by default.(Its style is set to display: none;)

The unit test says expect(alert).not.toBeVisible() which should pass, but it always fails regardless of what the v-alert model is set to. If i change the test to expect(alert).toBeVisible() it passes regardless of the v-alert model is set to true/false.

If i change the test to be expect(alert).toHaveStyle({ display: 'none' }); it fails regardless of if i have the model set to true/false.

So as far as i can tell the Jest unit test CANNOT determine the visibility of the v-alert component at all. These same test work fine on the v-btn component just fine so why does the v-alert break? This is just my first unit test sample that ive been trying to get working for 2 days now. I have an entire application to write tests for and so far Jest is not working very well with Vuetify...any suggestions?

// Test.vue component

<template>
  <div>
    <v-btn ref="btn" depressed tile @click="showAlert">Show Alert</v-btn>
    <v-alert
      v-model="showError"
      ref="error-msg"
      type="error"
      transition="scale-transition"
      width="410"
      tile
      dense
      dismissible
      @input="clearError"
    >
      {{ errorText }}
    </v-alert>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showError: false,
      errorText: ''
    };
  },
  methods: {
    showAlert() {
      this.errorText = 'Test Error message';
      this.showError = true;
    },
    clearError() {
      this.errorText = '';
    }
  }
};
</script>

// Jest Unit test

// Libraries
import Vue from 'vue';
import Vuetify from 'vuetify';

// Components
import Test from '@/components/Login/Test.vue';

// Utilities
import { createLocalVue, shallowMount } from '@vue/test-utils';

// Import Jest Dom test utils.
import '@testing-library/jest-dom';

const localVue = createLocalVue();

Vue.use(Vuetify);

describe('Test Page', () => {
  let vuetify;

  beforeEach(() => {
    vuetify = new Vuetify();
  });

  it('Check visibility of button', () => {
    const wrapper = shallowMount(Test, {
      localVue,
      vuetify
    });

    const btn = wrapper.findComponent({ ref: 'btn' }).element;

    expect(btn).toBeVisible();
  });

  it('Error Message hidden on page load', () => {
    const wrapper = shallowMount(Test, {
      localVue,
      vuetify
    });

    const alert = wrapper.findComponent({ ref: 'error-msg' }).element;

    expect(alert).not.toBeVisible();
  });
});

// Package.json

"dependencies": {
    "vue": "^2.6.11",
    "vue-click-outside": "^1.1.0",
    "vue-debounce": "^2.5.7",
    "vue-router": "^3.3.4",
    "vuetify": "^2.2.11",
    "vuex": "^3.4.0"
  },
  "devDependencies": {
    "@babel/plugin-transform-runtime": "^7.10.3",
    "@babel/polyfill": "^7.10.1",
    "@fortawesome/fontawesome-free": "^5.13.1",
    "@testing-library/jest-dom": "^5.10.1",
    "@vue/cli-plugin-babel": "^4.4.5",
    "@vue/cli-plugin-e2e-nightwatch": "^4.4.5",
    "@vue/cli-plugin-eslint": "^4.4.5",
    "@vue/cli-plugin-unit-jest": "^4.4.5",
    "@vue/cli-service": "^4.4.5",
    "@vue/eslint-config-prettier": "^4.0.1",
    "@vue/test-utils": "^1.0.3",
    "babel-eslint": "^10.0.3",
    "babel-jest": "^26.1.0",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^6.2.2",
    "node-sass": "^4.14.1",
    "sass": "^1.26.9",
    "sass-loader": "^8.0.2",
    "vue-cli-plugin-vuetify": "^2.0.6",
    "vue-template-compiler": "^2.6.11",
    "vuetify-loader": "^1.5.0"
  }
skyboyer
  • 22,209
  • 7
  • 57
  • 64
user616
  • 783
  • 4
  • 18
  • 44

1 Answers1

1

I ran into a similar issue, so I decided to use exists from @vue/test-utils instead.

Docs for exists: https://vue-test-utils.vuejs.org/api/wrapper/#exists

I also decided to use v-if (instead of v-model) on the v-alert element to hide / show the component.

It looks like if v-if receives a value of false, the component/element in the document is replaced with <!---->, which is great for checking if your component/element is hidden or displayed.

See v-if test spec: https://github.com/vuejs/vue/blob/52719ccab8fccffbdf497b96d3731dc86f04c1ce/test/unit/features/directives/if.spec.js


SFC

Template:

<template>
  <v-container>
    <v-btn
      @click='showError()'
      ref="showErrorButton">
      Show Error
    </v-btn>
    <v-alert
      v-if="errorEncountered"
      ref="errorAlert"
      colored-border
      type="error"
      elevation="2"
      >
      Oops! Something went wrong!
    </v-alert>
  <v-container>
<template>

Javascript:

export default {
  methods: {
    showError() {
      this.errorEncountered = true;
    }
  }
  data() {
    return {
      errorEncountered: false,
    };
  },
};

Whenever errorEncountered is updated, the v-alert component will show/hide depending on whether the value is true/false.


Tests

describe('Component', () => {
  let wrapper;

  beforeEach(() => {
    wrapper = mount(Component, {
      localVue,
      vuetify,
    });
  });

  describe('When component is mounted', () => {
    it('Then the default value for errorEncountered should be false', () => {
      expect(wrapper.vm.errorEncountered).toBeFalse();
    });
    
    it('Then the default state for the error alert should be hidden', async () => {
      const errorAlert = wrapper.find({ ref: 'errorAlert' });
      expect(errorAlert.exists()).toBeFalse();
    });

    describe('When an error is encountered', () => {
      it('Then errorEncountered should be set to true', async () => {
        const showErrorButton = wrapper.find({ ref: 'showErrorButton' });
        showErrorButton.trigger('click');
        await Vue.nextTick();
        expect(wrapper.vm.errorEncountered).toBeTrue();
      });

      it('Then error alert should be visible', async () => {
        const showErrorButton = wrapper.find({ ref: 'showErrorButton' });
        showErrorButton.trigger('click');
        await Vue.nextTick();
        const errorAlert = wrapper.find({ ref: 'errorAlert' });
        expect(errorAlert.exists()).toBeTrue();
      });
    });
  });
Mark
  • 480
  • 4
  • 11
  • Just going to add an extra note... In the fairly short amount of time that I've worked with VueJS, I have not seen `v-model` being used before for conditional rendering. It seems to me that the existing code has been written in a way that might not be easy to understand / maintain given that there seems to be clearer directives out there for this purpose. See the following link for VueJS docs on conditional rendering: https://vuejs.org/v2/guide/conditional.html – Mark Jul 09 '20 at 14:32
  • Ok, looking at this again, I see that Veutify suggests that `v-model` can be used on `v-alert` to hide / show it. Though I am unsure whether or not it sets the component to `display: none` upon being updated to false. I personally believe that `v-if` makes the code and tests more readable. Unless I **have** to use something, I tend to avoid things described as magical (in their own words from the docs), as it will cause headaches if and when they are not behaving as expected in the future. – Mark Jul 10 '20 at 10:58