Vue-Test-Utils Unknown custom element: <router-link> - vuejs2

I'm using Jest to run my tests utilizing the vue-test-utils library.
Even though I've added the VueRouter to the localVue instance, it says it can't actually find the router-link component. If the code looks a little funky, it's because I'm using TypeScript, but it should read pretty close to ES6... Main thing is that the #Prop() is the same as passing in props: {..}
Vue component:
<template>
<div>
<div class="temp">
<div>
<router-link :to="temp.url">{{temp.name}}</router-link>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
import { Prop } from 'vue-property-decorator'
import { Temp } from './Temp'
#Component({
name: 'temp'
})
export default class TempComponent extends Vue {
#Prop() private temp: Temp
}
</script>
<style lang="scss" scoped>
.temp {
padding-top: 10px;
}
</style>
Temp model:
export class Temp {
public static Default: Temp = new Temp(-1, '')
public url: string
constructor(public id: number, public name: string) {
this.id = id
this.name = name
this.url = '/temp/' + id
}
}
Jest test
import { createLocalVue, shallow } from '#vue/test-utils'
import TempComponent from '#/components/Temp.vue'
import { Temp } from '#/components/Temp'
import VueRouter from 'vue-router'
const localVue = createLocalVue()
localVue.use(VueRouter)
describe('Temp.vue Component', () => {
test('renders a router-link tag with to temp.url', () => {
const temp = Temp.Default
temp.url = 'http://some-url.com'
const wrapper = shallow(TempComponent, {
propsData: { temp }
})
const aWrapper = wrapper.find('router-link')
expect((aWrapper.attributes() as any).to).toBe(temp.url)
})
})
What am I missing? The test actually passes, it just throws the warning. In fact, here is the output:
Test Output:
$ jest --config test/unit/jest.conf.js
PASS ClientApp\components\__tests__\temp.spec.ts
Temp.vue Component
√ renders a router-link tag with to temp.url (30ms)
console.error node_modules\vue\dist\vue.runtime.common.js:589
[Vue warn]: Unknown custom element: <router-link> - did you register the
component correctly? For recursive components, make sure to provide the
"name" option.
(found in <Root>)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.677s
Ran all test suites.
Done in 6.94s.
Appreciate any help you can give!

Add the router-link stub to the shallow (or shallowMount) method options like this:
const wrapper = shallow(TempComponent, {
propsData: { temp },
stubs: ['router-link']
})
or this way:
import { RouterLinkStub } from '#vue/test-utils';
const wrapper = shallow(TempComponent, {
propsData: { temp },
stubs: {
RouterLink: RouterLinkStub
}
})
The error should go away after you do this.

With Vue 3 and Vue Test Utils Next (v4), it seems you just have to add your router (the return object from createRouter) as a plugin to your mountOptions:
import router from "#/router";
const mountOptions = {
global: {
plugins: [router],
},
};
https://next.vue-test-utils.vuejs.org/api/#global
Or a more full example:
import router from "#/router";
import Button from "#/components/Button.vue";
const mountOptions = {
global: {
mocks: {
$route: "home",
$router: {
push: jest.fn(),
},
},
plugins: [router],
},
};
it("Renders", () => {
const wrapper = shallowMount(Button, mountOptions);
expect(wrapper.get("nav").getComponent({ name: "router-link" })).toExist();
});
Note, in the example above I'm using a project setup with Vue CLI.

Worked for me:
[ Package.json ] file
...
"vue-jest": "^3.0.5",
"vue-router": "~3.1.5",
"vue": "~2.6.11",
"#vue/test-utils": "1.0.0-beta.29",
...
[ Test ] file
import App from '../../src/App';
import { mount, createLocalVue } from '#vue/test-utils';
import VueRouter from 'vue-router';
const localVue = createLocalVue();
localVue.use(VueRouter);
const router = new VueRouter({
routes: [
{
name: 'dashboard',
path: '/dashboard'
}
]
});
describe('Successful test', ()=>{
it('works', ()=>{
let wrapper = mount(App, {
localVue,
router
});
// Here is your assertion
});
});
Or you can try this:

const wrapper = shallow(TempComponent, {
propsData: { temp },
localVue
})

Related

VUE 3 JS : can't acces to my props in mounted

I have a problem in a component.
I receive an id (name : theIdPost) from a parent file of this component but when I would like to use it in the mounted(){} part , it tells me :
TS2339: Property 'theIdPost' does not exist on type '{...
I can print the id in template, no worries but to use it in the SCRIPT part it doesn't work.
the component file:
<template lang="fr">
// All my html
</template>
<script lang="ts">
import { computed } from 'vue';
import { store } from '../store/index';
export default{
name: 'comment',
props: {
theIdPost: Number,
theTxtPost: String,
theLike: Number,
},
setup() {
const myStore: any = store
const commentList = computed(() => myStore.state.commentList);
console.log("CommentList > " +commentList.value);
return { commentList };
},
mounted() {
const myStore: any = store;
myStore.dispatch("getComments",
{'id': this.theIdPost}
);
}
}
</script>
<style lang="scss">
#import "../scss/variables.scss";
// ..... the style part
</style>
Can you explain me why it doesn't work ?
Thanks
If you are using the composition API with the setup, you have to add the lifecycle hooks differently:
https://v3.vuejs.org/guide/composition-api-lifecycle-hooks.html
setup(props) {
const myStore: any = store
const commentList = computed(() => myStore.state.commentList);
console.log("CommentList > " +commentList.value);
onMounted(() => {
myStore.dispatch("getComments",
{'id': props.theIdPost}
);
})
return { commentList };
},
For Solution there is 2 points :
because I use vue 3 and setup in composition API , the lifecycle Hook is different and mounted => onMounted
setup(props) {
const myStore: any = store
const commentList = computed(() => myStore.state.commentList);
onMounted(() => {
myStore.dispatch("getComments",
{'id': props.theIdPost}
);
})
return { commentList };
},
when we use onMounted, is like when we use ref(), we have to import before. So at the beginning of the SCRIPT part, we have to write :
import { onMounted } from 'vue';
So my final script is :
<script lang="ts">
import { computed, onMounted } from 'vue';
import { store } from '../store/index';
export default {
name: 'comment',
props: {
theIdPost: Number,
theTxtPost: String,
theLike: Number,
},
setup(props) {
const myStore: any = store;
const commentList = computed(() => myStore.state.commentList);
onMounted(() => {
myStore.dispatch("getComments",
{ 'id': props.theIdPost }
);
})
return { commentList };
},
}
</script>
Thanks to Thomas for the beginning of the answer :)
it worked for me too. i was setting up the setup and not pass props in to the setup. now okay

Access $moment inside async fetch of NuxtJS

I am using NuxtJS version 2.12.2.. I am trying to access $moment inside async fetch but it is returning app.$moment is not a function. Below is my snippet:
pages/_key.vue
<template>
<div></div>
</template>
<script>
import { mdSign } from '#/constants/encryption.js'
var pickerValue = new Date()
var dTime = pickerValue.getTime()
export default {
async fetch({ store, params, app }) {
const theUuid = app.$generateUUID() // this is a global property
const theSignature = mdSign({
uuid: theUuid
})
const body = {
cpKey: params.key,
day: app.$moment(pickerValue).format('YYYY-MM-DD'),
sign: theSignature,
time: dTime
}
await store.dispatch('example/fetchHistory', body)
}
}
</script>
<style lang="scss" scoped></style>
plugins/filter.js
import Vue from 'vue'
import { translations } from '#/constants/pinyin.js'
const moment = require('moment')
Vue.use(require('vue-moment'), {
moment
})
nuxt.config.js
export default {
...
...
plugins: [
'~/plugins/filters.js',
...
...
]
...
...
Add #nuxtjs/moment to your project:
npm i #nuxtjs/moment
Add #nuxtjs/moment to your nuxt.config.js modules:
modules: [
'#nuxtjs/moment'
]
There are Nuxtjs specific packages for many of the libraries you would typically use in a Vue app.

Vue unit tests - cannot read property 't' of undefined

I'm using jest to run vue unit tests to check the output of individual components. The component uses vuetify.
I create an instance of vue to mount the component:
import myComponent from './MyComponent.vue';
import VueRouter from 'vue-router';
import Vuetify from 'vuetify';
describe('Component', () => {
let wrapper;
const router = new VueRouter({
base: '/ui/',
routes: [
{
name: 'myRoute',
path: '/route-to-my-component',
component: myComponent
},
]
});
beforeEach(() => {
const localVue = createLocalVue();
localVue.use(VueRouter);
localVue.use(Vuetify);
wrapper = mount(myComponent, {
localVue: localVue,
router
});
});
it('contains a container', () => {
expect(wrapper.contains('v-container')).toBe(true);
})
});
I expect this test to pass, but instead I'm getting TypeError: Cannot read property 't' of undefined.
For reference, I was following https://fernandobasso.github.io/javascript/unit-testing-vue-vuetify-with-jest-and-vue-test-utils.html.
A couple of (somewhat random) things are needed in order to run vue unit test on top of vuetify:
Avoid using createLocalVue(), per this comment.
Some components (like Autocomplete) need $vuetify.lang and $vuetify.theme to be mocked
Your spec should look like:
import Vuetify from 'vuetify';
import Vue from 'vue';
Vue.use(Vuetify);
it('contains a container', () => {
const wrapper = mount(FreeAutocomplete, {
created() {
this.$vuetify.lang = {
t: () => {},
};
this.$vuetify.theme = { dark: false };
}
});
expect(wrapper.contains('v-container')).toBe(true);
})

How to write test that mocks the $route object in vue components

I have a component that contains statement like this.$route.fullPath, how should I mock value of fullPathof $route object if I want to test that component?
I disagree with the top answer - you can mock $route without any issue.
On the other hand, installing vue-router multiple times on the base constructor will cause you problems. It adds $route and $router as read only properties. Which makes it impossible to overwrite them in future tests.
There are two ways to achieve this with vue-test-utils.
Mocking vue-router with the mocks option
const $route = {
fullPath: 'full/path'
}
const wrapper = mount(ComponentWithRouter, {
mocks: {
$route
}
})
wrapper.vm.$route.fullPath // 'full/path'
You can also install Vue Router safely by using createLocalVue:
Installing vue-router safely in tests with createLocalVue
const localVue = createLocalVue()
localVue.use(VueRouter)
const routes = [
{
path: '/',
component: Component
}
]
const router = new VueRouter({
routes
})
const wrapper = mount(ComponentWithRouter, { localVue, router })
expect(wrapper.vm.$route).to.be.an('object')
Best not mock vue-router but rather use it to render the component, that way you get a proper working router. Example:
import Vue from 'vue'
import VueRouter from 'vue-router'
import totest from 'src/components/totest'
describe('totest.vue', () => {
it('should totest renders stuff', done => {
Vue.use(VueRouter)
const router = new VueRouter({routes: [
{path: '/totest/:id', name: 'totest', component: totest},
{path: '/wherever', name: 'another_component', component: {render: h => '-'}},
]})
const vm = new Vue({
el: document.createElement('div'),
router: router,
render: h => h('router-view')
})
router.push({name: 'totest', params: {id: 123}})
Vue.nextTick(() => {
console.log('html:', vm.$el)
expect(vm.$el.querySelector('h2').textContent).to.equal('Fred Bloggs')
done()
})
})
})
Things to note:
I'm using the runtime-only version of vue, hence render: h => h('router-view').
I'm only testing the totest component, but others might be required if they're referenced by totest eg. another_component in this example.
You need nextTick for the HTML to have rendered before you can look at it/test it.
One of the problems is that most of the examples I found referred to the old version of vue-router, see the migrations docs, eg. some examples use router.go() which now doesn't work.
No answer was helping me out, So I dig into vue-test-utils documentation and found myself a working answer, so you need to import.
import { shallowMount,createLocalVue } from '#vue/test-utils';
import router from '#/router.ts';
const localVue = createLocalVue();
We created a sample vue instance. While testing you need to use shallowMount so you can provide vue app instance and router.
describe('Components', () => {
it('renders a comment form', () => {
const COMMENTFORM = shallowMount(CommentForm,{
localVue,
router
});
})
})
You can easily pass router and to shallow mount and it does not gives you the error. If you want to pass store you use:
import { shallowMount,createLocalVue } from '#vue/test-utils';
import router from '#/router.ts';
import store from '#/store.ts';
const localVue = createLocalVue();
And then pass store:
describe('Components', () => {
it('renders a comment form', () => {
const COMMENTFORM = shallowMount(CommentForm,{
localVue,
router,
store
});
})
})
This solution solved the following errors:
Cannot read property 'params' of undefined when using this.$route.params.id
Unknown custom element router-link
✔
Easiest method i found is to use localVue
import { createLocalVue, mount } from '#vue/test-utils';
import VueRouter from 'vue-router';
import Vuex from 'vuex';
import ComponentName from '#/components/ComponentName.vue';
// Add store file if any getters is accessed
import store from '#/store/store';
describe('File name', () => {
const localVue = createLocalVue();
localVue.use(VueRouter);
// Can also be replaced with route(router.js) file
const routes = [
{
path: '/path',
component: ComponentName,
name: 'Route name'
}
];
const router = new VueRouter({ routes });
// if needed
router.push({
name: 'Route name',
params: {}
});
const wrapper = mount(ComponentName, {
localVue,
router,
store
});
test('Method()', () => {
wrapper.vm.methodName();
expect(wrapper.vm.$route.path)
.toEqual(routes[0].path);
});
});
Hope it helps!!!
Why are all answers so complicated? You can just do:
...
wrapper = mount(HappyComponent, {
mocks: {
$route: { fullPath: '' }
},
})
...
You dont have to specifically "mock" a router. Your application can set VueRouter in the global vue scope and you can still make it do what you want in your tests without issue.
Read the localVue usage with VueRouter: https://vue-test-utils.vuejs.org/guides/#using-with-vue-router.
I am currently pulling in a complex router from our main app and am able to jest.spyOn() calls to router.push() as well as setting the path before the component is created running shallowMount() for some route handling in a created() hook.
The Workaround
// someVueComponent.vue
<template>
... something
</template>
<script>
...
data () {
return {
authenticated: false
}
},
...
created () {
if(!this.authenticated && this.$route.path !== '/'){
this.$router.push('/')
}
}
</script>
// someVueComponent.spec.js
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import { shallowMount, createLocalVue } from '#vue/test-utils'
import SomeVueComponent from 'MyApp/components/someVueComponent'
import MyAppRouter from 'MyApp/router'
import MyAppCreateStore from 'MyApp/createStore'
import merge from 'lodash.merge'
function setVueUseValues (localVue) {
localVue.use(Vuex)
localVue.use(VueRouter)
// other things here like custom directives, etc
}
beforeEach(() => {
// reset your localVue reference before each test if you need something reset like a custom directive, etc
localVue = createLocalVue()
setVueUseValues(localVue)
})
let localVue = createLocalVue()
setVueUseValues(localVue)
test('my app does not react to path because its default is "/"', () => {
const options = {
localVue,
router: MyAppRouter,
store: MyAppCreateStore()
}
const routerPushSpy = jest.spyOn(options.router, 'push')
const wrapper = shallowMount(SomeVueComponent, options)
expect(routerPushSpy).toHaveBeenCalledTimes(0)
})
test('my app reacts to path because its not "/" and were not authenticated', () => {
const options = {
localVue,
router: MyAppRouter,
store: MyAppCreateStore()
}
const routerPushSpy = jest.spyOn(options.router, 'push')
options.router.push('/nothomepath')
expect(routerPushSpy).toHaveBeenCalledWith('/nothomepath') // <- SomeVueComponent created hook will have $route === '/nothomepath' as well as fullPath
const wrapper = shallowMount(SomeVueComponent, options)
expect(routerPushSpy).toHaveBeenCalledWith('/') // <- works
})
The above is done with the idea that I need the $route state changed before SomeVueComponent.vue is created/mounted. Assuming you can create the wrapper and want to test that the component this.$router.push('/something') based on some other state or action you can always spy on the wrapper.vm instance
let routerPushSpy = jest.spyOn(wrapper.vm.$router, 'push') // or before hooks, etc
As of this writing there seems to be an open defect which keeps the following from working because vm.$route will always be undefined, making the above the only option (that I know of) as there is no other way to "mock" the $route because installing VueRouter writes read only properties to $route.
From the vue-test-utils docs https://vue-test-utils.vuejs.org/guides/#mocking-route-and-router:
import { shallowMount } from '#vue/test-utils'
const $route = {
path: '/some/path'
}
const wrapper = shallowMount(Component, {
mocks: {
$route
}
})
wrapper.vm.$route.path // /some/path
If your interested here is the github link to a reproduction of the issue: https://github.com/vuejs/vue-test-utils/issues/1136
All kudos to #SColvin for his answer; helped find an answer in my scenario wherein I had a component with a router-link that was throwing a
ERROR: '[Vue warn]: Error in render function: (found in <RouterLink>)'
during unit test because Vue hadn't been supplied with a router. Using #SColvin answer to rewrite the test originally supplied by vue-cli from
describe('Hello.vue', () =>
{
it('should render correct contents', () =>
{
const Constructor = Vue.extend(Hello);
const vm = new Constructor().$mount();
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App');
});
to
describe('Hello.vue', () =>
{
it('should render correct contents', () =>
{
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{ path: '/', name: 'Hello', component: Hello },
],
});
const vm = new Vue({
el: document.createElement('div'),
/* eslint-disable object-shorthand */
router: router,
render: h => h('router-view'),
});
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App');
});
});
Not needing to pass parameters in to the view I could simplify the component as the default render, no need to push and no need to wait nextTick. HTH someone else!
Adding to the great answer from #SColvin, here's an example of this working using Avoriaz:
import { mount } from 'avoriaz'
import Vue from 'vue'
import VueRouter from 'vue-router'
import router from '#/router'
import HappyComponent from '#/components/HappyComponent'
Vue.use(VueRouter)
describe('HappyComponent.vue', () => {
it('renders router links', () => {
wrapper = mount(HappyComponent, {router})
// Write your test
})
})
I believe this should work with vue-test-utils, too.
Take a look at this example using vue-test-utils, where I'm mocking both router and store.
import ArticleDetails from '#/components/ArticleDetails'
import { mount } from 'vue-test-utils'
import router from '#/router'
describe('ArticleDetails.vue', () => {
it('should display post details', () => {
const POST_MESSAGE = 'Header of our content!'
const EXAMPLE_POST = {
title: 'Title',
date: '6 May 2016',
content: `# ${POST_MESSAGE}`
}
const wrapper = mount(ArticleDetails, {
router,
mocks: {
$store: {
getters: {
getPostById () {
return EXAMPLE_POST
}
}
}
}
})
expect(wrapper.vm.$el.querySelector('h1.post-title').textContent.trim()).to.equal(EXAMPLE_POST.title)
expect(wrapper.vm.$el.querySelector('time').textContent.trim()).to.equal(EXAMPLE_POST.date)
expect(wrapper.vm.$el.querySelector('.post-content').innerHTML.trim()).to.equal(
`<h1>${POST_MESSAGE}</h1>`
)
})
})
This is what I've been doing as per this article:
it('renders $router.name', () => {
const scopedVue = Vue.extend();
const mockRoute = {
name: 'abc'
};
scopedVue.prototype.$route = mockRoute;
const Constructor = scopedVue.extend(Component);
const vm = new Constructor().$mount();
expect(vm.$el.textContent).to.equal('abc');
});
You can mock to vm.$router by setting vm._routerRoot._router
For example
var Constructor = Vue.extend(Your_Component)
var vm = new Constructor().$mount()
var your_mock_router = {hello:'there'}
vm.$router = your_mock_router //An error 'setting a property that has only a getter'
vm._routerRoot._router = your_mock_router //Wow, it works!
You can double check their source code here: https://github.com/vuejs/vue-router/blob/dev/dist/vue-router.js#L558
Easiest way i've found is to mock the $route.
it('renders $router.name', () => {
const $route = {
name: 'test name - avoriaz'
}
const wrapper = shallow(Component, {
mocks: {
$route
}
})
expect(wrapper.text()).to.equal($route.name)
})

Vue Router router-view error

I'm getting the following error when trying to implement vue-router.
Unknown custom element: <router-view> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
Where do I need to provide the name option?
A lot of the tutorials I'm looking at seem to be an older version of vue-router. I follow the set-up process but can't get it to work.
Might there be something special I have to do when using the webpack cli template?
I'm also using the vue-router cdn.
Here's my main.js
import Vue from 'vue'
import App from './App'
import ResourceInfo from '../src/components/ResourceInfo'
var db = firebase.database();
var auth = firebase.auth();
const routes = [
{ path: '/', component: App },
{ path: '/info', component: ResourceInfo }
]
const router = new VueRouter({
routes
})
/* eslint-disable no-new */
var vm = new Vue({
el: '#app',
components: { App },
created: function() {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// Get info for currently signed in user.
console.log(user);
vm.currentUser = user;
console.log(vm.currentUser);
} else {
// No user is signed in.
}
})
// Import firebase data
var quizzesRef = db.ref('quizzes');
quizzesRef.on('value', function(snapshot) {
vm.quizzes = snapshot.val();
console.log(vm.quizzes);
})
var resourcesRef = db.ref('resources');
resourcesRef.on('value', function(snapshot) {
vm.resources.push(snapshot.val());
console.log(vm.resources);
})
var usersRef = db.ref('users');
usersRef.on('value', function(snapshot) {
vm.users = snapshot.val();
console.log(vm.users);
})
},
firebase: {
quizzes: {
source: db.ref('quizzes'),
asObject: true
},
users: {
source: db.ref('users'),
asObject: true
},
resources: db.ref('resources')
},
data: function() {
return {
users: {},
currentUser: {},
quizzes: {},
resources: []
}
},
router,
render: h => h(App)
})
And my App.vue
<template>
<div id="app">
<navbar></navbar>
<resource-info :current-user="currentUser"></resource-info>
<router-view></router-view>
</div>
</template>
<script>
import Navbar from './components/Navbar'
import ResourceInfo from './components/ResourceInfo'
export default {
name: 'app',
props: ['current-user'],
components: {
Navbar,
ResourceInfo
}
}
</script>
<style>
</style>
In your main.js file, you need to import VueRouter as follows:
import Vue from "vue" // you are doing this already
import VueRouter from "vue-router" // this needs to be done
And below that, you need to initialize the router module as follows:
// Initialize router module
Vue.use(VueRouter)
Other than the above, I cannot find anything else missing in your code, it seems fine to me.
Please refer to the installation page in docs, under NPM section:
http://router.vuejs.org/en/installation.html
First install vue router by using "npm install vue-route" and follow the bellow in main.js
import Vue from 'vue'
import Router from 'vue-router'
import App from './App'
import ResourceInfo from '../src/components/ResourceInfo'
var db = firebase.database();
var auth = firebase.auth();
Vue.use(Router)
var router = new Router({
hashbang: false,
history: true,
linkActiveClass: 'active',
mode: 'html5'
})
router.map({
'/': {
name: 'app',
component: App
},
'/info': {
name: 'resourceInfo',
component: ResourceInfo
}
})
// If no route is matched redirect home
router.redirect({
'*': '/'
});
// Start up our app
router.start(App, '#app')
This might be solve your problem
You forgot to import vue-router and initialize VueRouter in main.js
import VueRouter from "vue-router"
Vue.use(VueRouter)