As @jackmellis suggested - we need a way to avoid polluting the global environment and leaking between tests.
@jackmellis suggested isolated Vue constructors, although I think this would require a change to core.
What are peoples thoughts?
Are you referring to
Be able to Vue.use(plugin) without it polluting the global environment and leaking between tests. Some sort of isolated Vue constructor perhaps?
?
We could provide a wrapper that tracks the calls to global components, directives, filters and that can later be called again to _uninstall_ everything. This is however limited to serialised tests. The isolated constructor is interesting, I wonder if we cannot get a _hacky_ version working without touching core
It should work by using an extended constructor with empty options:
const FreshVue = Vue.extend()
FreshVue.use(plugin)
const testedVM = new FreshVue({ ... })
Let me know if there are practical issues with this.
What do people think about adding it as an option to mount:
mount(Component, {
use: [plugin]
})
use would take an array of plugins and use it in a scoped Vue instance.
I like that, it's neat. Should we also account for configuration options?
Yeah I think if we go this route we'll need to add all of the global API to mount. Which will be a reasonable amount of work.
mount(Component, {
use: ['plugin'],
config: {
ignoredElements: ['my-custom-web-component', 'another-web-component']
}
})
Another option is to have a ScopedVue object (although with a better name!) -
import { ScopedVue } from 'vue-test-utils'
const scopedVue = new ScopedVue
scopedVue.use(plugin)
scopedVue.mixin('my-directive', function () {})
mount(Component, {
instance: scopedVue
})
What I thought about doing with Vuenit was to have an install method that exposed the scoped vue object before mounting the component:
mount(Component, {
install : function(ScopedVue){
ScopedVue.use('blahblah');
}
});
I don't know if that is the way to go, it means you don't have to account for the entire Vue api within the mount config.
I should also add that I found that an extended Vue object didn't work with mixins, directives, and components. So doing:
const Extended = Vue.extend(Component);
Extended.component('some-component', definition);
const vm = new Extended.$mount();
This did not use some-component registered with the extended component, it would always go back to the base Vue object for globally-registered stuff.
@jackmellis it seems to work: https://jsfiddle.net/yyx990803/b5a1zh6u/
@yyx990803 well in that case I stand corrected!
You can see the original discussion on this matter: https://forum.vuejs.org/t/vue-component-inheritence/9387
Perhaps the issue I had was that I was creating an extended vue, then registering a global component, then I was extending that extended vue again. I had assumed that it would go up the inheritence chain until it found what it was looking for, but maybe it just looks on the current class, then jumps straight to the base class?
Perhaps it's time the community stopped using global mixins. For example,
vue-apollo installs a global mixin to add $apollo to every component. In
the react world, this is solved with an HOC which would add the apollo
client to the props.
On Wed, Jun 7, 2017 at 2:28 AM, Jack notifications@github.com wrote:
@yyx990803 https://github.com/yyx990803 well in that case I stand
corrected!You can see the original discussion on this matter:
https://forum.vuejs.org/t/vue-component-inheritence/9387Perhaps the issue I had was that I was creating an extended vue, then
registering a global component, then I was extending that extended vue
again. I had assumed that it would go up the inheritence chain until it
found what it was looking for, but maybe it just looks on the current
class, then jumps straight to the base class?—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/vuejs/vue-test-utils/issues/2#issuecomment-306699420,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AACouh9MJe0uCsum0MLyWw9TK6Vy3foWks5sBkLygaJpZM4NvCf1
.
@blocka I think people find very useful using global mixins, plugins and components. For me personally injecting $apollo like you said seems more straight forward than a HOC. In my tests I can replace that with a mock very easily if I want to so I don't have to care too much about it's implementation.
After all I don't think you can stop people from using such a feature which is very widespread in Vue and used by many of vue developers.
The proposed API for options.use is problematic.
For example, Vuex must be installed before you can create a store, so this is not possible:
const store = new Vuex.Store({...})
mount(Component, { use: [Vuex] })
An alternative could be this pattern:
const CleanVue = Vue.extend()
CleanVue.use(Plugin)
mount(Component, { instance: CleanVue })
But there are also problems calling use on a vue.extend instance.
An extended instance doesn't have a version, which vuex relies on.
I'll look into this more over the next couple days
@yyx990803 I know that provide/inject did not exist at the time vuex was created, but if it used provide/inject, this would be less of a problem, as vuex can provide it's own mock provider, or creating a one is pretty trivial, and there would be no need to globally clobber ever vue instance.
@eddyerburgh that is one of several reasons I wrote mock-vuex, because Vuex has some test-unfriendly rules about instantiating and useing
@eddyerburgh so, why not inject the store in the wrapper as well? That would make specs even cleaner and that's the approach I took for Revue.
mount(Component, {use: [Vuex], store: storeObject}
Or we make it even implicit, but that of course can be trcky if people wanne use a different store type.
@codebryo I don't think we should add options for specific plugins.
store is already used by Vuez. ), right now a store passed to mount will be passed as component options - which means Vuex and Vue can both be tested. If we treat all option.store as Vuex, we make it impossible to test a Vuez store. Unless we also make an exception for that. But then another plugin might require a store option. I think it's a bad road to go down.
Although maybe we could add some kind of options specifically for vue-router and Vuex as they are official libraries. If we did we'd have to be explicit in the options that they are for Vuex and vue-router
mount(Component, {use: [Vuex], vuexStore , vueRouter }
Overall, I'm in favor of this API -
const CleanVue = Vue.extend()
CleanVue.use(Vuex)
const store = new Vuex.Store({...})
mount(Component, { instance: CleanVue, store })
It means we don't have to write code for specific plugins.
It also means we avoid adding options for everything you can pass to a Vue instance, like component, mixin, directive etc.
@eddyerburgh alright, I get your point and yes it was a concern of mine as well. Probably better to omit some automagic by default.
As the approach with the CleanVue Instance is optional I think we should be good, as Vue.use(Vuex) would still work for users who prefer it that way.
The API seems pretty clean so we should be good, agreed. :)
Oops, accidentally closed 😛
Ok great, I'll start working on it and get it added 👍
I've added a function that returns an extended Vue instance with some props that Vuex and Vue-router use (like version and config)
This way you can pass it to mount using the instance option, and mount will use the extended class as a base class.
So you can add components, mixins, and install components without polluting the global vue base class.
import { scopedVue, mount } from 'vue-test-utils'
const baseClass = scopedVued()
baseClass.use(Vuex)
const wrapper = mount(Component, {
instance: baseClass
})
wrapper.vm.$store // is defined
const freshWrapper = mount(Component)
wrapper.vm.$store // is undefined
Some things I'd like input on:
scopedVue is very clear, can anyone think of a better name? extendedVue, freshVue, cleanVue?instance. I think this should be renamed, but am not sure what to. I would love some input.For 1 & 2, how about createLocalVue, the verb reminding users that it's a function? Then the option could be called localVue, making it clear that it expects the result of createLocalVue. The "local" part serves to remind people not to use or pollute the global Vue.
- Vuex can't be installed on two instances. It uses the same Vue class each install
I guess we should add an install option to allow overwriting previous Vue instance like forceInstall: true in Vuex.
@kazupon I'm happy to make a PR 👍
This has been solved by createLocalVue
Just did this:
import { mount, createLocalVue } from 'vue-test-utils';
import Vuetify from 'vuetify';
const localVue = createLocalVue();
localVue.use(Vuetify);
describe('Question.spec.js', () => {
let cmp;
let template;
beforeEach(() => {
cmp = mount(Question, {
localVue,
propsData: {
question: {},
id: "/questions/6673",
store,
});
template = cmp.html();
});
And I get
TypeError: Cannot set property 'installed' of undefined
at Function.instance.use (node_modules/vue-test-utils/dist/vue-test-utils.js:1253:30)
at Object.<anonymous> (src/components/Question/Question.spec.js:8:10)
at Generator.next (<anonymous>)
at Promise (<anonymous>)
at Generator.next (<anonymous>)
at <anonymous>
~Thanks for the bug report, can you please make a new issue?~
I see you did. Thanks :)
Already did one.
On 27 Oct 2017 07:29, "Edd Yerburgh" notifications@github.com wrote:
Thanks for the bug report, can you please make a new issue?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/vuejs/vue-test-utils/issues/2#issuecomment-339876564,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABr9gjZm3259LAVj5_uL6yS_-fz-smKYks5swWpNgaJpZM4NvCf1
.
@yyx990803 I tried in your fiddle what you suggested in this thread
const FreshVue = Vue.extend()
FreshVue.use(plugin)
const testedVM = new FreshVue({ ... })
but it seems to use the global Vue and it does not recognise $t method (error in console)
Hi @lsimone . The solution to this issue was to add a createLocalVue function. This needs to be used with a localVue option.
You can see examples in the docs—https://vue-test-utils.vuejs.org/en/api/createLocalVue.html 😀
Thanks @eddyerburgh but how to do it without importing createLocalVue from '@vue/test-utils'?
the code I quoted doesn't work as expected in the fiddle I linked
You can see an example in the createLocalVue code—https://github.com/vuejs/vue-test-utils/blob/dev/packages/test-utils/src/create-local-vue.js
You can omit the errorHandler
ok, thanks @eddyerburgh I will try this way.
So the code suggested by Evan here is definitely not enough to avoid Vue.use polluting global Vue instance.
Do you think that cloning Vue would have a big impact on performance? (I am using it in server side rendering)
Most helpful comment
What do people think about adding it as an option to mount:
usewould take an array of plugins and use it in a scoped Vue instance.