1
votes

I am currently experiencing a behaviour when testing my Vue Application (specifically when vuetify is included). I am using Jest as Test Runner but experienced the same behaviour with mocha.

The first thing to notice is that the problem only occurs with mount from the @vue/test-utils and not shallowMount. Also it only occurs if you use mount twice (I guess the reason is the pollution of the Vue Object but more on that later).

Now my component is manly just a wrapper around a basic v-data-table with the property value bound to its items and some custom slots for checkboxes instead of text.

Now the problem. First this is what the first variant of my test looks like (it's basically how it's recommended by vuetify. take a look here. As the test itsself doesn't really matter I'll just expect true to be true here

import Vue from 'vue';
import Vuetify from 'vuetify';
import { mount, createLocalVue, shallowMount } from '@vue/test-utils';

import  PermissionTable from '@/components/PermissionTable.vue';
import { expect } from 'chai';

const localVue = createLocalVue();

// Vue.use is not in the example but leaving it will cause the error that 
// the data table is not registered
Vue.use(Vuetify);

describe('Permissiontable.vue', () => {
  let vuetify;
  let tableItems;

  beforeEach(() => {
    tableItems = [];
    vuetify = new Vuetify();
  });


  it('will test the first thing',() => {
    const wrapper = mount(PermissionTable, {
      localVue,
      vuetify,
      propsData: {
        value: tableItems
      }
    });

    expect(true).to.be(true);
  });


  it('will test the second thing',() => {
    const wrapper = mount(PermissionTable, {
      localVue,
      vuetify,
      propsData: {
        value: tableItems
      }
    });

    expect(true).to.be(true);
  });
});

Now as already commented without using Vue.use(Vuetify) I'll get the error that the component v-data-table is not registered. With it I'm left with the following behaviour

  1. Test the first thing runs as expected and succeeds
  2. The the second thing fails the following Error

Type Error: Cannot read property '$scopedSlots' of undefined

and fails at mount(....). To make the behaviour even weirder, if I debug and stop at this line, run the mount manually in the debug console it fails as well on the first time with the same error. If I run it again it works. If I for example execute this test 4 times this will happen. 1st will succeed -> 2nd fails -> 3rd and 4th will succeed again.

Now I am sure that functions behave the same way if they get the same input. So the Input to the mount must be altered by the first call. My guess is that the Vue class gets polluted somehow. So if I look at the documentation for localVue this utility is made to prevent pollution of the global Vue class. So I altered my code to

import Vue from 'vue';
import Vuetify from 'vuetify';
import { mount, createLocalVue, shallowMount } from '@vue/test-utils';

import  PermissionTable from '@/components/PermissionTable.vue';
import { expect } from 'chai';

describe('Permissiontable.vue', () => {
  let vuetify;
  let tableItems;
  let localVue;

  beforeEach(() => {
    tableItems = [];
    localVue = createLocalVue();
    vuetify = new Vuetify();
    localVue.use(vuetify);
  });


  it('will test the first thing',() => {
    const wrapper = mount(PermissionTable, {
      localVue,
      vuetify,
      propsData: {
        value: tableItems
      }
    });

    expect(true).to.be(true);
  });


  it('will test the second thing',() => {
    const wrapper = mount(PermissionTable, {
      localVue,
      vuetify,
      propsData: {
        value: tableItems
      }
    });

    expect(true).to.be(true);
  });
});

So I create a new Instance of localVue and vuetify for every test. and make localVue use vuetify. Now this brings me back to the error

[Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.

I also experimented with various alterations of injecting vuetify (instantiated) or Vuetify. using the global Vue.use etc. At the end I'm always left with one of those two behaviours.

Now the workouround seems to be to write each test in a single file which works but I think is really bad practice and I want to understand what exactly happens here.

I also created Issues for Vuetify and Vue-Test-Utils now as I can't imagine this is expected behaviour, as well as making a stripped down Version of my Component with a test as a Repo

rm-test-mount-fails-on-second-exec

To Reproduce:

  1. Clone Repo
  2. npm install
  3. npm run test:unit

Versions:

Vue: 2.6.10
Vue-Test-Utils: 1.0.0-beta.29
Vuetify: 2.1.12

2
Weird enough. If I add the same test a 3rd and 4th time those tests run as well. so it's always the second one that fails after that all is okrelief.melone

2 Answers

1
votes

The bad news

Turns out this is indeed a bug in vue-test-utils at the moment. After I opened up issues I discovered another issue with a problem pretty similar to mine and I'm pretty sure the root cause for this is the same as in my case. Apperently this is due to a change that happend in the vue utils in v.beta.29

The issue can be found here #1130

The good News

There is a workaround to get your tests working again until this bug is resolved. You need to mount with the option sync: false so mounting in the top example would look like

const wrapper = mount(PermissionTable, {
  localVue,
  vuetify,
  propsData: {
    value: tableItems
  },
  sync: false
});

I still think this is a serious bug as identical tests should behave in the same way every time you run them no matter the settings. I will update this post as soon as there is news that this has been addressed.

0
votes

The only thing that seems out of place in your first piece of code is that you are writing Vue.use(Vuetify) and also using an instance of Vuetify when doing mount.

I suggest that you keep the Vue.use(Vuetify) and mount your component like this:

const wrapper = mount(PermissionTable, {
   localVue,   // vuetify is removed from this object
   propsData: {
     value: tableItems
   }
});

On a side note, unit tests generally should use shallowMount. I am not aware of your use case, but, if possible, please use it instead of mount