vuejs / test-utils Goto Github PK
View Code? Open in Web Editor NEWVue Test Utils for Vue 3
Home Page: https://test-utils.vuejs.org
License: MIT License
Vue Test Utils for Vue 3
Home Page: https://test-utils.vuejs.org
License: MIT License
Currently we have pretty good typings. But there are some limitations.
If there is a ts
file that exports defineComponent
, all is well. For example:
const Foo = defineComponent({
props: {
bar: { type: String }
}
})
// in a test
mount(Foo, {
props: {
bar: 123 // ide warning! bar should be a string
}
})
However we cannot get this level of type safety when importing a vue
. file, since it's not a regular TS file (needs to be parsed by SFC compiler first), or at least this is the case with this kind of simple shim:
declare module '*.vue' {
import { ComponentOptions } from 'vue'
var component: ComponentOptions
export default component
}
What are our options? π€
interface Props
from the SFC and make mount
take a generic param? Eg mount<Props>(...)
. Not really ideal but an option we can consider.This is very common but not well documented or supported in VTU beta. Let's add lots of tests around popular third party libraries, so people can easily test their apps with those.
I wonder if any of the big libs have upgraded to Vue 3 alpha... π€
combines setSelected
, setChecked` etc as per #17 (comment)
We need to allow people to mock things out. Not sure how this will look since we don't know how things will be attached to Vue.prototype in Vue 3 yet (or do we?)
Relevant: https://github.com/vuejs/vue-test-utils/blob/dev/test/specs/mounting-options/mocks.spec.js
As Vue 3 now allows having components with multiple root components, aka fragments, we need to figure out what to do with methods like:
Of course some of these will depend on the outcome of #17
I am testing some things out locally, and vm.$el
does not work with multi root components.
I noticed that Plugins is the first time in docs where we mention VueWrapper
, WrapperAPI
and so on.
Should they be mentioned before, in a standalone section? Should we explain them here? Should we just link to somewhere, and assume anyone reading Plugins is an advanced-enough user?
Useful for plugin authors
Vue has awesome docs, but the VTU docs are underwhelming. Let's change that.
I'm thinking three major sections. The docs refresh is basically as big a project as building the library itself, and just as important. It really needs someone who has great attention to detail and a passion for education/communication. If someone is excited about this, speak up!
Docs are here: https://github.com/lmiller1990/vue-testing-framework/tree/master/packages/docs
nextTick
, it's a core principle. no need to go too deep on how/why, but emphasis when you need to call it and whyThere are a few properties currently that modify the vm returned from createApp
, but could be confusing, as the mounted vue component may have the same properties.
Currently these are:
We should support
mount: {
stubs: {
ComponentToStub: true // default stub
Another: h => h('div') // custom stub
}
}
This is an scenario I've faced several times:
let's say your app has this global component which is used almost everywhere. Also, you mount your whole app in a weird DOM node because reasons (<-- really contrived example, just bear with me)
Instead of setting up the same stuff over and over, you might want to do this:
test/utils/custom-mount.js
:
import merge from 'lodash.merge'
import { mount as VTUMount } from '@vue/test-utils'
import SomeGlobalComp from '@/components/SomeGlobalComp'
function mount(Component, options) {
const mergedOptions = merge({}, options, {
attachTo: document.getElementById('render-point'),
global: {
components: { SomeGlobalComp }
}
})
return VTUMount(Component, mergedOptions)
}
export { mount }
and then import the custom mount
function from this helper file instead.
Real world scenarios:
createApp(...).mount
requires a real DOM element from what I can see - I'm not sure how VTU beta handled this, but I believe the default was NOT to mount on the dom, since we had an attachToDocument
option.
We are currently tightly coupled to the DOM - not ideal, but I think it's fine for a pre alpha.
Original: vuejs/vue-test-utils#1216
We currently have a helper file in our codebase we pull in to testing files, it contains this beauty:
/**
* Create a component stub that will render all included slots from the
* parent component. This lets you test the slots you've included in child component
* without having to fully mount the child component.
*
* * Notes on implementation *
* There is no one place this is clearly laid out. This thread gave the starting point:
*
* https://github.com/vuejs/vue-test-utils/issues/85
*
* but modifying the function requires understanding Vue's functional components
* and the `render` function:
*
* https://vuejs.org/v2/guide/render-function.html
*
* especially the arguments that `createElement` can take:
*
* https://vuejs.org/v2/guide/render-function.html#createElement-Arguments
*
* In short, this produces a <div> with only the child components slots, but none of
* its other functionality.
*
* @return {object} The functional component definition
*/
componentStubWithSlots: function () {
return {
render: function (createElement) {
return createElement('div', [Object.values(this.$slots)]);
}
};
}
It would be nice if there was a more elegant way of handling this, or at least better documented (not requiring links to 3 different places).
That helper function gets used like so:
test('Show only mine checkbox adds showOnlyMine to the query params', async () => {
const wrapper = shallow(reservationsList, {
store,
localVue,
stubs: {
RouterLink: RouterLinkStub,
'base-table': helpers.componentStubWithSlots()
},
mocks: { $ga }
});
let checkbox = wrapper.find('[data-test="showOnlyMineCheckbox"]');
checkbox.trigger('click');
expect(wrapper.vm.paramsForGettingListItems)
.toEqual({ showOnlyMine: true });
});
I think it would be nicer if there was something like that built in, so we could just do this:
test('Show only mine checkbox adds showOnlyMine to the query params', async () => {
const wrapper = shallow(reservationsList, {
store,
localVue,
stubs: {
RouterLink: RouterLinkStub
},
stubComponentSlots: [
'base-table'
],
mocks: { $ga }
});
let checkbox = wrapper.find('[data-test="showOnlyMineCheckbox"]');
checkbox.trigger('click');
expect(wrapper.vm.paramsForGettingListItems)
.toEqual({ showOnlyMine: true });
});
We use this helper a lot, it seems like the kind of common "utility" that would be in Vue-Test-Utils.
here is the source
https://github.com/vuejs/vue-test-utils/blob/228cd1ad4c578d71a0d05e7d7e491ce8b90229a9/packages/test-utils/src/wrapper.js#L361
here is the tests
https://github.com/vuejs/vue-test-utils/blob/dev/test/specs/wrapper/setChecked.spec.js
Just port this to TypeScript, seems fairly easy. Since find
is now generic, we can write like this
expect(wrapper.find<HTMLInputElement>('input').element.checked).toBe(true)
and get IDE recommendations for the checked
property on element... pretty cool!
I wonder if we can move to Puppeteer or something, looks like PhantomJS is deprecated π€
functionality exists but naming is wrong (provides, should be provide)
I know this repo might be temporary (are we planning on just renaming this one and deprecating the other one?), but should be set some sort of CI pipeline?
I'd go with GitHub Actions, which is quite easy to set and everything is stored in the same repo.
It would:
I propose the following API for alpha-0. Progress is tracked here: https://github.com/vuejs/vue-test-utils-next/projects/1
Each of these has a ticket on the project board.
shallowMount
is not required for an alpha.
This is intentionally a minimal API. The primary way users will interact with their components is DOMWrapper in alpha.
This is the bulk of the functionality.
We will also need to release an alpha-0 for vue-jest. Branch here: https://github.com/vuejs/vue-jest/tree/next. If we don't provide this, people won't be able to test SFCs.
We should provide some examples on how to do things. We should also make sure it's possible to test things that were previously pain points, or new features:
Now we have separated find and findComponent, and considering DOMWrapper and VueWrapper have different APIs, I think ErrorWrapper should be separated accordingly, too.
I ran into this complication when writing a plugin. Better typedefs will make extending VTU easier.
When triggering events in the component with the new emit
method, they don't appear on the wrappers emitted
method
Here is a link to the source code, you can reproduce it by removing .skip
https://github.com/blacksonic/todomvc-vue-composition-api/blob/master/src/components/item/item.spec.js#L35
If you don't destructure the { emit }
from ctx
, and do ctx.emit
instead, it works. destructured emit won't capture the events in wrapper.emitted()
. It call the original emit
, not the one we override in the mixin.
Failing test: 1acd0e4
Vue 3 docs will use codepen, I believe, for the examples, so people can try it out. I wonder if this is something we could do, too, and if that would be a good idea.
#105 had some great discussion around helpers - the example raised there was for components with async setup
functions (used in <Suspense>
). Testing those alone won't work, since if you have a async setup
, Vue expected a <Suspense>
wrapper.
We could provide a helper (this works)
test('uses a helper to mount a component with async setup', async () => {
const Comp = defineComponent({
async setup() {
return () => h('div', 'Async Setup')
}
})
const mountSuspense = async (component: new () => ComponentPublicInstance, options) => {
const wrapper = mount(defineComponent({
render() {
return h(Suspense, null, {
default: h(component),
fallback: h('div', 'fallback')
})
}
})
...options)
await flushPromises()
return wrapper
}
const wrapper = mountSuspense(Comp)
console.log(wrapper.html()) //=> <div>Async Setup</div>
})
Some thoughts:
flushPromises
for the user. Any possible side-effects/unexpected behavior?shallowMountSuspense
? Someone will almost certainly ask for this.
shallowMount
as a stand-alone function (which I think we should) and instead have a shallow: true
mounting option, this would not be a problem.I think we are lacking tests for functional components. Those types of components resemble allot more statefull components now.
A functional component signature is now - https://github.com/vuejs/rfcs/blob/master/active-rfcs/0031-attr-fallthrough.md#functional-components
const Component = (props, { slots, attrs, emit }) => h('div', { class: ['a'] }, slots)
Component.props = { /*...*/ }
Component.inheritAttrs = false
Component.displayName = 'FunctionalComponent'
I can take this over and try to add test cases on as many places as I can.
We also need to test asyncFunctionalComponents - https://github.com/vuejs/rfcs/blob/async-component/active-rfcs/0026-async-component-api.md
Will probably rely on #3. I have some ideas on how to handle this.
Instead of shallowMount
, I think we should have a shallow
mounting option (discussed internally).
const Comp = mount({
shallow: true
})
shallowMount
was pretty buggy in VTU beta. It should completely stub out and child components. So:
<template>
<div>Content</div>
<Foo />
<Bar>
<div>BAR</div>
</Bar>
</template>
Should render as follows when using shallow
:
<div>Content</div>
<foo-stub />
<bar-stub />
Whether it's <FooStub />
or <foo-stub>
/> doesn't matter, the point is that the components are no longer rendered, nor is any of their code executed.
Things to consider:
find
a stubbed out component? Feels a bit weird to try and find a component that is purposefully NOT rendered. But it might be a common use casefind
does not support find(Component)
syntax. I'm not sure it should - it was impossible find
. One such issue: vuejs/vue-test-utils#1272 andFollwing up on a discussion on Discord, I wanted to open this issue to gather feedback, experiences, and opinions on how to handle async testing and the usage of nextTick
and flushPromises
.
nextTick
and flushPromises
I couldn't explain it any better than here: testing-library/react-testing-library#11
We're currently returning nextTick
on several methods.
I think I was expecting to get away with await wrapper.something() most of the time.
by @cexbrayat. Turns out you might need an indeterminate number of nextTick
calls to get everything up to date, so you end up using flushPromises
. Happened the same to me while writing docs for HTTP requests.
Also, there's this question by @JessicaSachs that is worth checking/considering:
nextTick
might safely be able to be the default? I don't remember if this prohibits testing of async created hooks (I'm thinking in Vue 2)
flushPromises
The pros is that users have one less thing to worry about βΒ and a thing which is quite low-level.
On the other hand, we might lose the ability to test intermediate states (such as "Loading states") before the promise resolved/rejected. If that's the case, this is a no-go.
Pros is that current implementation of VTU-next would work.
Cons is that we all know that explaining something in a documentation does not translate well with "our users will know about this and will take it into account" π
nextTick
and flushPromises
Not sure about the shape of that tool, but something along the lines of waitFor. Other alternatives mentioned on Discord are wrapper.findAsync
.
disclaimer: this issue is meant to serve as a place to discuss alternatives. After that, if we reach a consensus, I open to write a proper RFC with the suggested proposal π
Found this when trying to use VTU inside of Vite. Ended up patching the modules.
<script type="module">
import '@vue/test-utils'
</script>
will fail because we depend on two modules that use commonjs exports in our es bundle:
The changes I had to make to get them to work are here:
https://github.com/JessicaSachs/vite-component-test-starter/blob/master/patches
To recreate a failure, you can...
As per vuejs/rfcs#151 (comment)
We need to keep setData
and setProps
. or have something similar. I think only supporting the top level wrapper
is fine for now, since we don't have find(Comp)
yet.
Like in VTU beta. Currently only supports the object syntax. Eg we should be able to do
stubs: [Foo, 'bar']
https://vue-test-utils.vuejs.org/api/options.html#stubs
Beta only supported passing names, but I don't see why we shouldn't support passing components as well. This mirrors the other syntax, eg
stubs: {
Foo: FooStub
}
VTU should be quite plug-and-play as long as you install it in a Vue project (what other projects would want to use VTU? π€).
Thus, this section should be really thin so people can start to write tests as soon as possible.
Thoughts:
@vue/cli
and its VTU plugin (should we discuss this?)We'd like to use DOMWrapper inside of our plugins like the Data Test ID Plugin we reference in our docs
Slots are a pain point in VTU beta. We should make sure there are tests for all the cases, and they all work as expected.
Ref: slots specs in VTU beta: https://github.com/vuejs/vue-test-utils/blob/dev/test/specs/mounting-options/slots.spec.js
We should port the ones that are relevant to this codebase.
NOTE: slots and scoped slots are now the same thing in Vue 3.
Since the composition API is going to be a big part of Vue 3, we need to have good support for that. We should allow something like:
mount(Foo, {
context: {
store: {} // mock vuex store?
emit: () => {} // not sure we should let people override the default options or not?
attrs: {} // is this a use case? does it make sense to let users mess with attrs? probably not?
}
})
not sure about overwriting emit
and attrs
, but definitely we should let people pass arbitrary keys like store
and router
, for example.
This seems pretty important. How did we forget?
When trying to integrate with Mocha's test reporter and running VTU in Vite, I found that VTU was blowing away my test reporter π
I was trying to alter the keyup event as with the old version, but the new API doesn't have an options argument yet.
Here is a link to the source code, you can reproduce it by removing .skip
https://github.com/blacksonic/todomvc-vue-composition-api/blob/master/src/components/header/header.spec.js#L15
Need to implement a build step to compile everything to a distributable JS file.
Thank you for releasing this alpha so we can give it a spin!
Using alpha.1
in a TypeScript project throws a compilation error:
ERROR Failed to compile with 2 errors 14:58:14
error in /Users/ced-pro/Code/vtu-next-test/node_modules/@vue/test-utils/dist/components/RouterLinkStub.d.ts
ERROR in /Users/ced-pro/Code/vtu-next-test/node_modules/@vue/test-utils/dist/components/RouterLinkStub.d.ts(1,23):
1:23 Cannot find type definition file for 'src/vue-shims'.
> 1 | /// <reference types="src/vue-shims" />
| ^
2 | export declare const RouterLinkStub: new () => import("vue").ComponentPublicInstance<{
3 | to: any;
4 | } & {}, unknown, unknown, {}, {}, Record<string, any>, import("vue").VNodeProps & {
error in /Users/ced-pro/Code/vtu-next-test/node_modules/@vue/test-utils/dist/mount.d.ts
ERROR in /Users/ced-pro/Code/vtu-next-test/node_modules/@vue/test-utils/dist/mount.d.ts(9,9):
9:9 Property 'default' of type 'string | VNode<RendererNode, RendererElement> | { render: Function; } | undefined' is not assignable to string index type 'Slot'.
7 | props?: Record<string, any>;
8 | slots?: {
> 9 | default?: Slot;
| ^
10 | [key: string]: Slot;
11 | };
12 | global?: {
This can be easily reproduced in @lmiller1990 demo repo (even if the Vue CLI setup reports a more detailled error):
git clone https://github.com/lmiller1990/vtu-next-demo.git
cd vtu-next-demo
yarn
yarn tsc
This was buggy in beta, let's make sure it works here. eg; import a component async and use it.
I have a Foo.vue
<template>
<div> FOO </div>
</template>
<script>
async setup () {
return {}
}
</script>
<template>
<Suspense>
<template #default>
<Foo />
</template>
<template #fallback>
Fallback
</template>
</Suspense>
</template>
<script lang=ts">
import Foo from './Foo.vue'
export default {
components: {
Foo
}
})
</script>
And wrapper.html()
returns undefined
. π€
Let's agree on a basic API and make an RFC so everyone can express their thoughts.
method | status | notes |
---|---|---|
attributes | keep | |
classes | keep | |
exists | keep | |
contains | keep | |
destroy | keep | |
emitted | keep | |
find | keep | |
findAll | keep | |
html | keep | |
name | keep | |
trigger | keep | now returns nextTick . So you can do await wrapper.find('#button').trigger('click') . Nice! |
setChecked | keep | see trigger |
setSelected | keep | see trigger |
setValue | keep | see trigger |
vm | keep | |
element | keep |
method | status | explanation |
---|---|---|
emittedByOrder | deprecate | emitted serves this purpose |
get | deprecate | This was added recently. similar to find - can we combine them? |
is | deprecate | just use native tagName property |
isEmpty | deprecate | Available via custom matcher, difficult to get 100% right |
isVisible | deprecate | Available via custom matcher, difficult to get 100% right |
isVueInstance | deprecate | |
props | deprecate | Anti-pattern. Test what a prop does, not its presence or value. |
setData | deprecate | Anti-pattern. Use data mounting option to set a specific state. |
setMethods | deprecate | Anti-pattern. Vue does not support arbitrarily changing methods, nor should VTU |
setProps | deprecate | See setData , setMethods |
text | deprecate | use toContain |
method | status | explanation |
---|---|---|
data | keep | |
slots | keep | |
scopedSlots | deprecate | Slots are scoped by default in Vue 3. Use slots . |
context | deprecate | No longer needed |
stubs | keep | |
provide | keep | |
mixins | new | attach mixins by app.mixin , since you no longer attach these to Vue.prototype |
plugins | new | attach mixins by app.mixin , since you no longer attach these to Vue.prototype |
[Vue warn]: Component is missing template or render function.
at <Anonymous ref="VTU_COMPONENT" >
at <VTUROOT>
import test from 'ava'
import { mount } from '@vue/test-utils'
import { createApp } from 'vue/dist/vue.cjs.js'
test('expected behaviour computed prop lifecycle - no reactivity', async t => {
const vue = createApp({
template: `<pre>{{ myComputed }}</pre>`,
computed: {
myComputed () {
return 1
},
},
})
const wrapper = mount(vue)
// @ts-ignore
console.log(`wrapper.vm.myComputed β `, wrapper.vm.myComputed)
})
git clone https://github.com/vue-sync/simple-store
cd simple-store
npm i
npm run test test/internal/vueComputed.ts
Please let me know if I need to give more background or explain what it is exactly what I'm trying to do and why!
Hi, I'm trying to use the Vue test utils with the AVA test runner.
I didn't use vue-test-utils with Vue 2 yet, but trying to get into the habit for Vue 3.
I found this example of how to mock the the browser variables like document
for Vue test utils for Vue 2:
https://github.com/eddyerburgh/vue-test-utils-ava-example/blob/master/test/helpers/setup.js
Can someone help me with a minimal setup for [email protected]
or above & ava v3.x?
That original example is quite different from what ava suggests here:
https://github.com/avajs/ava/blob/master/docs/recipes/browser-testing.md
I wonder what the key differences are and how to best set it up for Vue 3.
find(Component)
move plugins
and mixins
to global
Almost every page on docs have some code snippets. The question is:
How can we make sure that these examples are working as expected? How can we make sure that these examples will continue to work as expected? It would be really bad to provide broken, outdated samples.
For now, I've been writing samples in VTU-next repo, running them along the tests. However, this scales poorly, and there's no way we can ask contributors to do so (and, even then, make sure they did).
Is there a way to import code snippets from somewhere else, where they can be run and tested?
Are there other alternatives?
By adding these properties, we allow the user to provide global directives and components, as they would in their own apps.
components
to the MountOptions.global
propertydirectives
to the MountOptions.global
propertyWe should allow users to register default global components, directives and plugins, instead of passing to each test all the time.
In VTU beta we could add a few global configs. Stubs was the main one with a few more later added.
Components, filters and directives were easy to add globally to all components, you just had to do this in your setup file:
Vue.use(Plugin)
Vue.component(GlobalComponent)
// etc
With Vue 3, you now have a Vue App instance, which means you no longer can add global configuration like that. This would lead to allot of repetition in tests, and large codebases could suffer greatly.
We could provide the same api as we have now in MountOptions.global
, just at a global level.
We should have some additional tests to the unit tests. There is the example app.
Something like:
yarn build
This will make sure there are no surprises for the end user.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.