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);
})
I'm trying to test a component that uses a child component WarnOnUnsavedModal. I'm not trying to test the child component, however.
The child component uses <b-modal>, and in node_modules, that imports a component called bBtn.
When I try to run my test file, it fails with the following message:
import bBtn from '../button/button';
^^^^
SyntaxError: Unexpected identifier
My test file:
import BootstrapVue, { bBtn } from 'bootstrap-vue';
import { mount, createLocalVue } from '#vue/test-utils';
import ComponentName from '../ComponentName.vue';
const localVue = createLocalVue();
localVue.use(BootstrapVue);
describe('ComponentName', () => {
it('Has props', () => {
const wrapper = mount(ComponentName, {
store,
createLocalVue,
stubs: {
ModalWarnOnSave,
'b-btn': bBtn,
},
propsData: {
resourceType: 'General',
},
});
expect(1 + 1).toBe(2);
});
});
I've tried adding a line like this to the stubs:
stubs: {
ModalWarnOnSave: true,
},
Why isn't that component being picked up here? I've tried swapping out localVue.use() with Vue.use(), to no avail.
What do I need to do to get this test to run? I'm happy to ignore the child file that's causing the problem.
Try this:
import BootstrapVue, { BButton } from 'bootstrap-vue'
bBtn is not the exported name of the b-button component. b-btn is an alias component name when Vue.use'ing BootstrapVue.
See https://bootstrap-vue.js.org/docs/components/button#importing-individual-components
How am i able to mock $parent for my specs? When using shallowMount with my component i always get clientWidth/clientHeight of undefined. I already tried mocking $parent as an object with an $el as a key and two more nested keys for clientWidth and clientHeight, but that's not working as expected. I cannot figure out the right usage of parentComponent.
I've got a single file component as seen below:
<template>
<img :src="doSomething">
</template>
<script>
export default {
name: "Foobar",
data() {
return {
size: null
};
},
computed: {
doSomething() {
# here is some string concatenation etc.
# but not necessary for example
return this.size;
}
},
created() {
let parent = this.$parent.$el;
this.size = `size=${parent.clientWidth}x${parent.clientHeight}`;
}
};
</script>
creating the vue app looks like this:
import Vue from "vue";
import Foobar from "./Foobar";
const vueEl = "[data-vue-app='foobar']";
if (document.querySelector(vueEl)) {
new Vue({
el: vueEl,
components: {
"foo-bar": Foobar
}
});
}
and the combination of using slim with my component looks like this:
div data-vue-app="foobar"
foo-bar
This is my test setup:
import { shallowMount } from "#vue/test-utils";
import Foobar from "#/store/Foobar";
describe("Foobar.vue", () => {
let component;
beforeEach(() => {
component = shallowMount(Foobar, {});
});
The parentComponent mounting option expects a component object:
import Parent from "../Parent.vue";
...
beforeEach(() => {
component = shallowMount(Foobar, {
parentComponent: Parent
});
});
Also, try pinning the version of vue-test-utils to "^1.0.0-beta.28". This should allow your tests to complete, but the clientWidth/Height will still be 0x0
ref: https://vue-test-utils.vuejs.org/api/options.html#parentcomponent
It's making it impossible for me to add Vue as a view system to a framework called Nest with Express.
I didn't think that adapting Vue was so complicated. That's why I'm here so that you can guide me on the right path and I won't use Vue directly.
Fist the error:
[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
(found in <Root>)
app.controller.ts
import { Controller, Get, Render, Response } from '#nestjs/common';
import { createRenderer } from 'vue-server-renderer';
import { createApp } from './app';
import HelloComponent from './components/Hello';
const context = {data: {}, view: '', componets: {} };
#Controller()
export class AppController {
#Get()
getHello(#Response() res): any {
context.data = { message: 'Esto es un nuevo mensaje 2' };
context.componets = { 'hello' : HelloComponent };
const app = createApp(context);
const renderer = createRenderer();
renderer.renderToString(app, (err, html) => {
res.end(html);
});
}
}
import { createApp } from './app';
import Vue from 'vue';
export function createApp(context: any) {
return new Vue({
data: context.data,
template: fs.readFileSync('./index.html', 'utf-8'),
components: context.components,
}).$mount('#app');
}
I try is to have a base template and then add the components for each controller or route with NestJS.
I don't know if this is possible and if I'm forced to use Webpack, since I'm not currently using it.
Thanks!
Vue launched an entire site to walk you through getting your server side rendering up and running. It is NOT the same process that is outlined at https://vuejs.org.
Complete information can be found at: https://ssr.vuejs.org/ and is referenced in the main guide about halfway down the sidebar navigation under the heading serverside rendering https://v2.vuejs.org/v2/guide/ssr.html
Here is the gist of it to get you started:
npm install express --save
npm install vue vue-server-renderer --save
Integrating with your server example
const Vue = require('vue')
const server = require('express')()
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
const app = new Vue({
data: {
url: req.url
},
template: `<div>The visited URL is: {{ url }}</div>`
})
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>${html}</body>
</html>
`)
})
})
server.listen(8080)
Rendering a Vue Instance
// Step 1: Create a Vue instance
const Vue = require('vue')
const app = new Vue({
template: `<div>Hello World</div>`
})
// Step 2: Create a renderer
const renderer = require('vue-server-renderer').createRenderer()
// Step 3: Render the Vue instance to HTML
renderer.renderToString(app, (err, html) => {
if (err) throw err
console.log(html)
// => <div data-server-rendered="true">Hello World</div>
})
// in 2.5.0+, returns a Promise if no callback is passed:
renderer.renderToString(app).then(html => {
console.log(html)
}).catch(err => {
console.error(err)
})
Thankfully it's not that complicated of an issue.
You are attempting to use the runtime build on .ts files, which you cannot. This is because only *.vue because they are pre-compiled.
To get around this, simply create an alias to vue in webpack:
resolve: {
alias: {
vue: 'vue/dist/vue.js'
}
}
Which will give you access to the template-compiler allowing you to use Vue inside of non pre-compiled templates (read: any file not ending in .vue)
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)
})