Closed kimond closed 6 years ago
@blake-newman would you be able to write a guide on using vue-test-utils with TypeScript?
Would love this. I'm following a blog post by Alex Joverm but failing to get it to work at all.
@RehanSaeed the blog post of Alex Joverm doesn't use TypeScript.
The main issue, is that wrapper.vm
is typed as a generic Vue
instance. This should probably typed as the component instance in use. So you can correctly use wrapper.vm
in knowledge about what properties are available.
Everything is as you would expect, following the guides.
Just following up @blake-newman's explanation, wrapper.vm
is typed as Vue
because TypeScript cannot know the component type in .vue
files - we usually annotate them like this to avoid compilation error https://github.com/Microsoft/TypeScript-Vue-Starter#single-file-components. If the components are written in normal .ts
file, wrapper.vm
should be inferred correctly.
Vetur (and probably WebStorm) have their own language service which can deal with .vue
files but they only affect .vue
files. That means components imported in .vue
are typed correctly while components imported in .ts
are not.
To be available typed components in .ts
, we should use TypeScript plugin vue-ts-plugin or generate .d.ts
for each .vue
file by using vuetype. But they are still experimental and might not be able to be used in practical projects.
I think it would be the best if Vetur supports TypeScript plugin so that we can use its feature in .ts
files including type inference for .vue
files.
I have recently started writing Vue unit tests using Jest + Typescript + Vue test utils, however i'm facing an issue where passing in my component object into the shallow function appears differently when using vue with typescript.
I believe the reason is because it's using the default constructor of vue (vue-shims.d.ts) and not the typed component. As explained by @ktsn.
Is there a way to pass in the component object correctly using typescript?
Source code to reproduce the issue: https://github.com/mikeli11/Vue-Typescript-Jest
Using typescript:
students.vue
Student.Test.ts
Without typescript (https://github.com/vuejs/vue-test-utils-jest-example)
@kimond What does your Jest configuration look like? My setup cannot even resolve the modules in tests. I have ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
in the config, but importing components from *.vue files in tests only gives a Cannot find module
error.
@tlaak Here is my jest configuration. However, I did nothing special in order to make it works.
"jest": {
"moduleFileExtensions": [
"ts",
"js",
"json",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest",
"^.+\\.ts$": "<rootDir>/node_modules/ts-jest/preprocessor"
},
"verbose": true
}
I forgot to mention that I needed to revert to vue-test-utils 1.0.0-beta.12
since I got some issues with vue-test-utils 1.0.0-beta.13
. Since I didn't try the latest version of vue-test-utils.
@kimond That looks like the same I have. I noticed that the module resolution fails if I have my tests in the tests/
directory. When I move a test file under src/
it will pass.
@tlaak It seems like your tsconfig.json
is not configured correctly. Only the files in includes (and not in excludes) are compiled and tested. Here is mine for reference:
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"node",
"jest"
],
"paths": {
"@/*": [
"src/*"
]
}
},
"include": [
"src/**/*.ts",
"src/**/*.vue",
"tests/**/*.ts"
],
"exclude": [
"node_modules"
]
}
@kimond
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["dom", "es5", "es2015"],
"module": "es2015",
"moduleResolution": "node",
"noImplicitAny": false,
"noImplicitReturns": true,
"outDir": "./built",
"sourceMap": true,
"strict": true,
"strictNullChecks": false,
"strictPropertyInitialization": false,
"target": "es5"
},
"include": ["src/**/*", "test/**/*"]
}
My imports are working just fine when I'm running the app in browser. I have the baseUrl
set to .
so I can use paths like import foo from 'src/components/foo'
, but these are not working in tests either. I need to use relative import paths there.
@tlaak You need to set a moduleNameMapper in your Jest config. Here an example.
"jest": {
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/frontend/src/$1",
},
}
With the config above you will be able to use src/components/foo
from your tests.
@kimond Thanks a million! That helped. I think this conversation verifies that proper 'how to' documentation is really needed :)
Yes we definitely need a how to guide. Would you like to help make a guide?
If a TypeScript user would like to write a guide, you can make a PR and I will help you with the process :)
I think the guide could take setup parts from Jest, Typescript then Vue-test-utils from their own guide then combine everything into a guide. Another possibility would be to make a quick guide that refers to each setup guides using simple links then add a complete example for Vue-test-utils.
The second option is quickest but less detailed than the first one.
I would like to give it a go. Should I try adding a cookbook to Vuejs.org in a Pull Request?
@elevatebart you could make a pull request in this repo, we have a guides section in the docs.
Is there a guide available here?
I've finally had some time to write a guide on using with TypeScript—https://deploy-preview-876--vue-test-utils.netlify.com/guides/using-with-typescript.html.
Le me know what you think, and if there's any information missing that you would like me to add!
If I can add my two cents I would say TypeScript users would benefit more from the guide in the sense of how to use the library, not how to configure it, as Vue CLI already handles that. Some pitfalls to have into consideration from vue-test-utils + TypeScript:
it('foo', () => {
const wrapper = mount(Component)
const foundComponent = wrapper.find({ name: 'SomeComponent' })
foundComponent.vm.$emit('event')
// We don't know the signature of wrapper.vm, so TS fails when we try to access bar
expect((wrapper.vm as any).bar).toBeFalsy()
})
describe('actions', () => {
let store: Store<RootState>
beforeEach(() => {
const localVue = createLocalVue()
localVue.use(Vuex)
store = new Vuex.Store({ modules: { langs: langsStore } })
})
it('should load a default language', async () => {
store.state.langs.language = 'es'
store.dispatch('langs/loadDefaultLanguage')
await flushPromises()
expect(store.state.langs.language).toEqual('en')
})
})
describe('mutations', () => {
let store: Store<GlobalState>
beforeEach(() => {
const localVue = createLocalVue()
localVue.use(Vuex)
// We need to cast to any :(
store = new Vuex.Store(globalStore as any)
})
it('should change the globlal state loading to true when enable loading mutation is called', () => {
store.commit(globalMutationTypes.LOADING.ENABLE)
expect(store.state.loading).toBeTruthy()
})
})
interface CustomProperties extends CSSStyleDeclaration {
Color: string
Size: string
}
describe('Icon', () => { snapshotTest({ component: Icon })
it('should propagate correctly the color of the icon', () => { const wrapper = shallowMount(Icon, { propsData: { name: 'ui-edit', color: 'red' } })
expect((wrapper.find('.icon').element.style as CustomProperties).Color).toEqual('var(--red)')
}) })
I would say in general the guide should be focused in how to avoid at all costs casting to any, and when there is no other way. Maybe when the base guide is completed I could add more to it 🙂
@cesalberca I've added everything that I can to the guide, so if you can add more info/pitfalls that would be great!
Any method, computed or prop of a component must be casted to any when doing assertions with those:
That sounds like a problem with our types that we should fix?
Yeah, it can be done @eddyerburgh. Although I don't know where can I make a PR. Perhaps next week 👍
@cesalberca @eddyerburgh for now, I personally have solved it by using the $data property to access data
properties, not sure if that could be another option for the docs for now? It doesn't give me any types of course, but that way it can be avoided to cast every wrapper.vm as any, as each property of wrapper.vm.$data is any by default Record<string, any>
@cesalberca Why do we have to do this:
// We don't know the signature of wrapper.vm, so TS fails when we try to access bar
expect((wrapper.vm as any).bar).toBeFalsy()
Both mount<T>
and shallowMount<T>
have the generic argument T
which is the type of the component, so the vm
property should give us intellisense of all properties of the component but this does not actually work.
@RehanSaeed yes I expect it should be,
but currently I still got error on the TSLint of my VSCode:
Could you help clarify if this happened to you as well?
Hi @chenxeed,
as Blake and a few others explained above, it's currently not possible to infer the type of wrapper.vm, as it has a type of Vue, but to make it work, it must be able to somehow infer the type from inside the .vue component, which is currently not possible.
Yesterday after Vue.js London, I had the pleasure to talk to @DanielRosenwasser and he also confirmed, that currently the best ways are most probably to either use type assertion (any
).
Hope this clears it up a bit further.
Hi @chenxeed,
as Blake and a few others explained above, it's currently not possible to infer the type of wrapper.vm, as it has a type of Vue, but to make it work, it must be able to somehow infer the type from inside the .vue component, which is currently not possible.
Yesterday after Vue.js London, I had the pleasure to talk to @DanielRosenwasser and he also confirmed, that currently the best ways are most probably to either use type assertion (wrapper.vm).msg or to use vm.$data. (because vm.$data is typed as
any
).Hope this clears it up a bit further.
Hi @ivansieder, I also meet this kind of problem,it is about wrapper.vm.(methods),i want to test my methods in example.spec.ts,it tells the same issue , but it can work in example.spec.js it confused me about 3 days...why vm couldn't get a attribute like $methods..
Hi @chenxeed, as Blake and a few others explained above, it's currently not possible to infer the type of wrapper.vm, as it has a type of Vue, but to make it work, it must be able to somehow infer the type from inside the .vue component, which is currently not possible. Yesterday after Vue.js London, I had the pleasure to talk to @DanielRosenwasser and he also confirmed, that currently the best ways are most probably to either use type assertion (wrapper.vm).msg or to use vm.$data. (because vm.$data is typed as
any
). Hope this clears it up a bit further.Hi @ivansieder, I also meet this kind of problem,it is about wrapper.vm.(methods),i want to test my methods in example.spec.ts,it tells the same issue , but it can work in example.spec.js it confused me about 3 days...why vm couldn't get a attribute like $methods..
oh yearh,after i waitup,i know how to do: just let wrapper.vm as any... finally,what i realize is that:a good sleeping nap can clear up my mine
Add to your types.d.ts
:
import { Wrapper } from '@vue/test-utils'
declare module '@vue/test-utils' {
interface Wrapper {
readonly vm: any
}
}
Thank you @ivansieder for the confirmation, that really helps to clarify the current states.
Is there any roadmap that includes this to make this happened? Since in vue 3.0 most likely it'll support TS properly and the newly written components should able to have proper TS typing as well.
Add to your
types.d.ts
:import { Wrapper } from '@vue/test-utils' declare module '@vue/test-utils' { interface Wrapper { readonly vm: any } }
This "works" as in my IDE is not blowing up, but only patches the problem. I wish this Issue was not closed. Perhaps this question can be moved to another thread?
Update 5/8/2019 I got around this issue another way by using sinon instead of jest and declaring wrapper as "any"
describe('ComponentToTest.vue', () => {
let wrapper: any
beforeEach(() => {
wrapper = shallowMount(QuotesFind, { localVue })
})
afterEach(() => { sinon.restore() })
it('does not throw TypeScript errors', () => {
const spy = sinon.spy(wrapper.vm, 'myComponentMethod')
spy({ message: 'finally...')
expect(wrapper.vm.$data.myReaction).toBe('yesssss')
})
})
Add to your
types.d.ts
:import { Wrapper } from '@vue/test-utils' declare module '@vue/test-utils' { interface Wrapper { readonly vm: any } }
I created types.d.ts with this code and it doesn't work.
I get two ts erros:
All declarations of 'Wrapper' must have identical type parameters.
Subsequent property declarations must have the same type. Property 'vm' must be of type 'V', but here has type 'any'.
If I use wilsunson's suggested jest.spyOn((wrapper.vm as any), 'methodName'))
results in this error:
Cannot invoke an expression whose type lacks a call signature. Type 'SpyInstance<any, unknown[]>' has no compatible call signatures.
I've tried testing methods in class components and using Vue.extend. Neither work.
Does anyone else have any ideas? I've been at this on and off for a few days now.
Like @jayporta , I also can't use the types.d.ts
solution, I get the same errors. For data, I can use wrapper.vm.$data
, but for computed props and methods, the only thing that seems to work is (wrapper.vm as any).computedProp
.
I'd love to see a proper solution to this!
(I'm using jest@24.8.0, ts-jest@24.0,2, typescript@3.4.5, @vue/test-utils@1.0.0-beta.29, Vue class components)
Maybe you are interested in this finding https://github.com/vuejs/vue-jest/issues/188 And the corresponding wannabe-guide to setup Vue + TypeScript + Jest https://github.com/quasarframework/quasar-testing/issues/48#issuecomment-507763139
I am missing the above mentioned casting ((wrapper.vm as any)....
) from the documentation. It took me an hour to figure this out. Would it be worth adding this?
Here's something that works.
import YourComponentHere from "@/components/YourComponentHere.vue";
import { shallowMount, Wrapper } from "@vue/test-utils";
describe("test", () => {
let wrapper: Wrapper<YourComponentHere & { [key: string]: any }>;
it("does something", () => {
expect(wrapper.vm.someThingWhatever).toBe(true);
});
});
This is cool because:
This is not cool because:
& {[key: string]: any}
everytime you create a test fileI'm not sure if this'll work (just an idea I had) - to solve the above 'not cool' part you could also edit src/shims-vue.d.ts to look like this:
declare module '*.vue' {
import Vue from 'vue';
type VueRelaxed = Vue & {[key: string]: any}
export default VueRelaxed;
}
and then every .vue import will allow MyComponent.somethingNotExisting. Seems dangerous though
Can I add my (possible) solution to the discussion? Just don't use single file components. Make typescript in a separate file.
Foo.vue
<template>
<div></div>
</template>
<script src="./Foo.ts" lang="ts"></script>
Foo.ts
import {Component, Prop, Provide} from 'vue-property-decorator';
@Component
export default class Foo extends Vue{
}
In Webpack make .vue files resolve over .ts files.
resolve: {
extensions: ['.vue', '.ts', '.js', '.json']
}
Now you can import classes and inside Webstorm and other IDE's you will simply see them as imported from typescript files. Therefore intellisense will work. But when you compile Webpack prefers .vue and your app also works.
Noticable issues: For some reason sourcemap of Foo.vue messes with the sourcemap of Foo.ts and there are issues with code coverage and jest test debugging. Can someone can give me a hint on how to resolve it?
Here's something that works.
import YourComponentHere from "@/components/YourComponentHere.vue"; import { shallowMount, Wrapper } from "@vue/test-utils"; describe("test", () => { let wrapper: Wrapper<YourComponentHere & { [key: string]: any }>; it("does something", () => { expect(wrapper.vm.someThingWhatever).toBe(true); }); });
This is cool because:
- you don't have to cast wrapper.vm to any everytime you use it
- it keeps the existing properties of Vue (Intellisense still suggests "$data" etc.) but allows properties that don't exist
This is not cool because:
- You need to add the
& {[key: string]: any}
everytime you create a test file
@3nuc I'm confused by 2 things:
Wrapper<YourComponentHere>
come with all the types needed?Wrapper<YourComponentHere & { [key: string]: any }>
and Wrapper<Vue & { [key: string]: any }>
?@vegerot
- Why doesn't Wrapper
come with all the types needed?
From what I know, it's difficult to extract this information from an SFC .vue file. @ znck is doing God's work regarding this in typescript-plugin-vue, though it's currently experimental
I assume this is why all .vue
imports are cast by TS to the generic Vue
interface which doesn't contain prop names etc.
If you want to know where this is done in your project, look for the shims-vue.d.ts
file in your /src
folder. You'll find the below snippet there:
declare module '*.vue' { //for every .vue file
import Vue from 'vue';
export default Vue; //assume it's default import is of type Vue (generic interface without type information)
}
- Given 1, what's the difference between Wrapper<YourComponentHere & { [key: string]: any }> and Wrapper<Vue & { [key: string]: any }>?
There's no difference. Both YourComponentHere
and Vue
are of type Vue
. I wrote YourComponentHere
because I guess it's more future-proof (if there's an ability to get .ts information from a .vue file, you wouldn't want your project to use Vue
iface everywhere in your tests). And since you already have to import the component to shallowMount
it, why not?
@3nuc interesting. I know you can access that information in the originating .vue
file with type a = MyComponent['yourPropName]
. But you're saying we squash that information in other files--why? Because it's difficult? And typescript-plugin-vue
will be able to add this info?
Also, in VSCode, TSServer can't tell me these types when writing code. But for example, at compile time I will get errors like
Property 'saveIfValid' is private and only accessible within class 'LROCreateWingEventDialog'.
Even though if I hover over that line in VSCode I get
So at least at compile time that info is gotten. But there's a disconnect between the build server and language server
I get the same errors while compiling as @vegerot and I'm also looking for a proper solution to accessing computed props and methods with TypeScript. Has there been any update or workable solution to this issue in the meantime?
I worked around it like this:
const wrapper = mount<Vue & {
someDataValue: number[];
}>(usage);
wrapper.vm.someDataValue // number[]
I still have the same issue :/
This approach works by also casting vm
property value
s to any
and is the most elegant one so far IMO:
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('computes [enhancedMsg]', () => {
const msg = 'new message'
const wrapper = shallowMount<HelloWorld>(HelloWorld, {
propsData: { msg }
})
expect(wrapper.vm.enhancedMsg).toBe(`${msg} is nice`)
})
})
@tehciolo for me this doesn't work: Cannot find name 'HelloWorld'.
Did you do anything more to get this to build?
@brmdbr These are the steps I followed:
vue-cli
with vue2 + TypeScript support.@vue/cli-plugin-unit-jest
(This comes with an example test that uses the already generated HelloWorld.vue
component)HelloWorld
named enhancedMsg
, based on the msg
propGetting real type information from .vue files that are imported in .spec.ts files is now possible in Vue 3 & Vite (maybe Vue 2 but I didn't try), but it probably requires significant changes to your/your team's workflow. If you really want it, here are the instructions:
tsserver
, since tsserver
's plugin architecture doesn't allow for easy extraction of type information from .vue filesThe above two steps should make autosuggestions work in wrapper.vm.
in your text editor. It'll make the "does not exist on wrapper.vm" errors go away (since those things now are extracted from the .vue file and exist on wrapper.vm)
example repo where this works — just clone, run npm install
then npm run test
. Keep in mind I'm using some alpha packages like vue-jest@5 etc.
@sethidden
I cannot easily move to Vue3 (and cannot move to Vite at all). I also cannot expect every dev to use VSCode, so I need a solution that works not only in the editor.
Unfortunately, there does not seem to be a (good) solution for my use case to get full type inference.
@tehciolo Like Vetur, Volar is a language server so it's editor agnostic (though both are optionally available as VS Code extensions). There's a IDE support list in Volar's readme. As you can see, my screenshots are from (neo)vim, not vscode.
Volar also works with Vue 2 but requires additional setup but I see there've been some problems with vue-test-utils, volar and vue 2
With Vue 2 another roadblock could be getting Vue CLI to use vue-tsc instead of just tsc — would probably require messing with the webpack configuration or disabling vue-cli-plugin-typescript
@sethidden I've got the type information working for the props by using Volar's Take Over Mode. But I can't seem to get any information on my methods or data variables in the component. Just wondering if this is something you have managed to get working?
I have managed to get everything working (computed, methods, variables etc) if I don't use the setup attribute on the script tag, but using that only props seem to work
I'm not sure if this issue belongs to this project. However, I'm using vue-test-utils since the beginning (even when its name was Avoriaz). But I have some issue to use SFC with typescript and Jest. I was wondering if you planned to write more documentation about which test runners or libs we should use to work properly with Typescript + Jest + Vue-test-utils?
Thank you!