Jest Unit Test: wrapper.vm.$refs.editForm.validate is not a function - vue.js

When I write test case for form submit, i m getting issue with 1wrapper.vm.$refs.editForm.validate is not a function
I am unable to figure out the problem.. please Help me.
"#vue/cli-plugin-babel": "^3.11.0", "#vue/cli-plugin-eslint": "^3.11.0", "#vue/cli-plugin-pwa": "^3.11.0", "#vue/cli-plugin-unit-jest": "^3.11.0", "#vue/cli-service": "^3.11.0", "#vue/eslint-config-prettier": "^5.0.0", "#vue/test-utils": "^1.0.0-beta.29", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^10.0.1", "babel-jest": "^23.6.0"
==== EditProperty.vue======
<v-form ref="editForm" lazy-validation>
<v-flex>
<v-text-field label="Label Text" v-model="labelName" :rules="[v => !!v || 'Label Text is required']"
/>
</v-flex>
</v-form>
<script>
export default {
data() {
return {
labelName: ''
}
},
methods: {
save() {
if (this.$refs.editForm.validate()) {
this.$emit('updateLable', this.labelName)
}
}
}
}
</script>
======EditProperty.spec.js =====
import { shallowMount, createLocalVue } from '#vue/test-utils'
import EditProperty from '#/components/EditProperty.vue'
import Vuetify from 'vuetify'
const localVue = createLocalVue()
localVue.use(Vuetify)
let wrapper
describe('EditProperty.vue', () => {
beforeEach(() => {
wrapper = shallowMount(EditProperty, {
localVue,
data() {
return {
labelName: 'Username'
}
}
})
})
it('should save called correctly', () => {
wrapper.vm.save()
})
})
expected => test should be pass
getting => wrapper.vm.$refs.editForm.validate is not a function
When I write test case for form submit, i m getting issue with 1wrapper.vm.$refs.editForm.validate is not a function
I am unable to figure out the problem.. please Help me.

shallowMount does not render the child components. I.E. in your case, v-form won't be rendered in the test. In fact if you call html from your wrapper, you will see a HTML comment in place of the <edit-form>.
The rationale behind that vue test utils feature is that, when you're unit testing a component, you test only the logic of such component in isolation, and don't rely on code from other modules.
Now you could manually pass an object as stub and provide any test double to allow the validate() call, via the stubs option:
import { shallowMount, createLocalVue } from '#vue/test-utils'
import EditProperty from '#/components/EditProperty.vue'
import Vuetify from 'vuetify'
const localVue = createLocalVue()
localVue.use(Vuetify)
let wrapper
describe('EditProperty.vue', () => {
beforeEach(() => {
const EditFormStub = {
render: () => {},
methods: {
validate: () => true,
}
};
wrapper = shallowMount(EditProperty, {
localVue,
stubs: {
'edit-form': EditFormStub,
}
data() {
return {
labelName: 'Username'
}
}
})
})
it('should save called correctly', () => {
wrapper.vm.save()
})
})
So we are passing a fake editForm as a stub, with a fake validate() method which always returns true.
Then you can test your component code. For instance, you could test that your label is emitted as updateLabel (in your original snippet it was 'updateLable', be wary):
it('should save called correctly', () => {
wrapper.vm.save();
expect(wrapper.emitted('updateLabel')[0][0]).toBe(whatever the label should be)
})

if you got in a situation where you don't need to stub. You could easily do this:
import { nextTick } from 'vue'
it('test', async () => {
wrapper.vm.$refs.editForm.validate = jest.fn()
await nextTick()
})

Related

Jest - Vue test utils - How to assert callback function of window eventlistener?

Im trying to check if a function is being call when resize event fired.
"#vue/test-utils": "^1.3.3"
"vue": "^2.7.14"
"jest": "27.1.0"
Component.vue
export default {
props: {},
data() {
this.timer = null,
this.stoppedForce = false
},
methods :{
manageTimers(){
if (this.$el.querySelector('[data-wrapper-adTVU]')){
clearTimeout(this.timer)
}else{
this.resetTimer()
}
},
resetTimer(){
clearTimeout(this.timer)
if (this.stoppedForce) {return}
this.timer = setTimeout( ()=> if(){}else{}, 2000)
}
},
mounted () {
window.addEventListener('resize', this.manageTimers)
}
}
Component.test.js
import myComponent from /.Componnet
import { shallowMount } from '#vue/test-utils'
describe('Component test', () => {
it('should trigger manageTimes', () => {
const wrapper = shallowMount(myComponent, {attachTo: window.document.body } )
const spyManager = jest.spyOn(wrapper.vm, 'manageTimes')
const event = new CustomEvent('resize')
window.dispatchEvent(event)
expect(window.addEventListener).toBeCalledWith('resize',expect.any(Function))
expect(spyManager).toHaveBeenCalled()
}
}
I have tried:
mocking the window.addEventListener but didn't work.
window.addEventListener = jest.fn((event, callback) => {
events[event] = callback
})
SpyOn window dispatch:
This worked to this expectations, but it is not what I need
const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent')
//same code of above for wrapper and dispatching event.
//Gives OK for:
expect(dispatchEventSpy).toHaveBeenCalledWith(expect.any(Event))
expect(dispatchEventSpy.mock.calls[0][0].type).toBe('resize')
Cheking timer value. But got Received: null.
expect(wrapper.vm.timer).not.toBeNull()
Need: I just wanna check if the callback function "manageTimes" from that window "resize" eventListener is being called.
Would appreciatte it if some of you could help me.
Thanks.

How in submit method of vuejs3/vee-validate 4 app get vuex object?

In vuejs3/vee-validate 4 app when form is submitted I try to send request with vuex and
failed, as I can not get access to store object. I do:
<template>
<Form #submit="onSubmit" :validation-schema="schema" class="login">
<label class="col-form-label" for="email">Email:</label>
<Field
id="email"
name="email"
type="email"
v-model="email"
class="form-control editable_field"
placeholder="Your email address"
autocomplete=off
/>
<ErrorMessage name="email" class="validation_error"/>
...
<label class="col-form-label" for="password">Password:</label>
<Field
id="password"
name="password"
type="password"
v-model="password"
class="form-control editable_field"
placeholder="Your password"
autocomplete=off
/>
<button>Submit</button>
</Form>
</template>
<script>
import appMixin from '#/appMixin'
import app from './../../App.vue'
import store from '#/main' // THAT DOES NOT WORK
import { /* ref, */ onMounted } from 'vue'
import { Field, Form, ErrorMessage } from 'vee-validate'
import * as Yup from 'yup'
import mitt from 'mitt'
const emitter = mitt()
export default {
name: 'loginPage',
mixins: [appMixin],
data: function () {
return {
email: 'email#site.com',
password: '111111'
}
},
components: {
Field,
Form,
ErrorMessage
},
setup () {
let self = this
const schema = Yup.object().shape({
email: Yup.string().email().required().label('Email Address'),
password: Yup.string().min(5).required().label('Your Password')
})
const loginInit = async () => {
console.log('loginInit emitter::')
console.log(emitter)
}
function onSubmit (credentials) {
alert(JSON.stringify(credentials, null, 2))
console.log('this::')
console.log(this) // UNDEFINED
console.log('self::')
console.log(self) // UNDEFINED
console.log('app::')
console.log(app) // I see in console : https://prnt.sc/vf1gx3
console.log('store::')
console.log(store) // UNDEFINED
app.$store.dispatch('login', credentials)
.then(() => this.$router.push('/'))
// this.$router.push({path: '/admin/tasks'}) // DEBUGGING
// .then(() => this.$router.push('/admin/tasks'))
.catch(error => console.log(error))
}
onMounted(loginInit)
return {
schema,
onSubmit
}
}
}
</script>
in src/main.js I have :
import { createApp } from 'vue'
import { createStore } from 'vuex'
import axios from 'axios'
import App from './App.vue'
import router from './router/router.js'
import { settingCredentialsConfig } from '#/app.settings.js'
const store = createStore({
state () {
return {
status: '',
token: localStorage.getItem('token') || '',
...
So in console I got error :
Uncaught (in promise) TypeError: Cannot read property 'dispatch' of undefined
I see in console : https://prnt.sc/vf1gx3
package.json:
{
"dependencies": {
"axios": "^0.20.0-0",
"bootstrap": "^4.3.1",
"core-js": "^3.6.5",
"font-awesome": "^4.7.0",
"jquery": "^3.4.1",
"mitt": "^2.1.0",
"moment-timezone": "^0.5.31",
"node-sass": "^4.12.0",
"popper.js": "^1.16.0",
"sass-loader": "^10.0.4",
"vee-validate": "^4.0.0-beta.18",
"vue": "^3.0.0",
"vue-router": "^4.0.0-rc.1",
"vuex": "^4.0.0-rc.1",
"yup": "^0.29.3"
},
}
src/main.js :
import { createApp } from 'vue'
import { createStore } from 'vuex'
import axios from 'axios'
import App from './App.vue'
import router from './router/router.js'
import 'bootstrap'
import 'font-awesome/css/font-awesome.css'
import { settingCredentialsConfig } from '#/app.settings.js'
const store = createStore({
state () {
return {
status: '',
token: localStorage.getItem('token') || '',
user: null,
}
},
mutations: {
auth_request (state) {
state.status = 'loading'
},
auth_success (state, data) {
state.status = 'success'
state.token = data.token
localStorage.setItem('token', data.token)
state.user = data.user
localStorage.setItem('user', JSON.stringify(data.user))
},
auth_error (state) {
state.status = 'error'
}
},
actions: {
login ({ commit }, userCredentials) { // Login action
return new Promise((resolve, reject) => {
commit('auth_request')
let apiUrl = process.env.VUE_APP_API_URL
console.log('+login userCredentials::')
console.log(userCredentials)
console.log('+login settingCredentialsConfig::')
console.log(settingCredentialsConfig)
console.log('+login apiUrl::')
console.log(apiUrl)
// alert('apiUrl::' + apiUrl)
axios.post(apiUrl + '/login', userCredentials, settingCredentialsConfig)
.then((response) => {
if (typeof response.data.access_token === 'undefined' || typeof response.data.user === 'undefined') {
commit('auth_error') // call auth_error mutation to make changes to vuex store
// bus.$emit('authLoggedError')
return
}
const token = response.data.access_token
const user = response.data.user
axios.defaults.headers.common['Authorization'] = token
commit('auth_success', {
token: token,
user: user
}) // call auth_success mutation to make changes to vuex store
// bus.$emit('authLoggedSuccess', user)
resolve(response)
})
.catch((error) => {
commit('auth_error') // call auth_error mutation to make changes to vuex store
localStorage.removeItem('token')
// bus.$emit('authLoggedError')
reject(error)
})
})
}, // login ({ commit }, user) { // Login action
addUser (context, payload) {
context.commit('addUser', payload)
}
},
getters: {
user (state) {
return state.user
}
}
})
const app = createApp(App)
app.use(store)
app.use(router)
app.mount('#app')
Since you're using the composition api, you should use the useStore hook
import {useStore} from 'vuex'
export default {
setup(){
const store=useStore();
store.dispatch("action",payload);//instead of app.$store.dispatch("action",payload)
}
...

Jest Unit test cant determine Vuetify components visibility

I have a Vue2 project with Vuetify, and i am using Jest for unit testing my code. I am starting out testing some sample code and i simple cannot get Jest to determine if a Vuetify v-alert component is visible or not. I have tried the built in Jest methods as well as adding Jest-dom and using the toBeVisible() method and nothing is working so far.
If you look at the Test.vue component, the v-alert component is hidden by default.(Its style is set to display: none;)
The unit test says expect(alert).not.toBeVisible() which should pass, but it always fails regardless of what the v-alert model is set to. If i change the test to expect(alert).toBeVisible() it passes regardless of the v-alert model is set to true/false.
If i change the test to be expect(alert).toHaveStyle({ display: 'none' }); it fails regardless of if i have the model set to true/false.
So as far as i can tell the Jest unit test CANNOT determine the visibility of the v-alert component at all. These same test work fine on the v-btn component just fine so why does the v-alert break? This is just my first unit test sample that ive been trying to get working for 2 days now. I have an entire application to write tests for and so far Jest is not working very well with Vuetify...any suggestions?
// Test.vue component
<template>
<div>
<v-btn ref="btn" depressed tile #click="showAlert">Show Alert</v-btn>
<v-alert
v-model="showError"
ref="error-msg"
type="error"
transition="scale-transition"
width="410"
tile
dense
dismissible
#input="clearError"
>
{{ errorText }}
</v-alert>
</div>
</template>
<script>
export default {
data() {
return {
showError: false,
errorText: ''
};
},
methods: {
showAlert() {
this.errorText = 'Test Error message';
this.showError = true;
},
clearError() {
this.errorText = '';
}
}
};
</script>
// Jest Unit test
// Libraries
import Vue from 'vue';
import Vuetify from 'vuetify';
// Components
import Test from '#/components/Login/Test.vue';
// Utilities
import { createLocalVue, shallowMount } from '#vue/test-utils';
// Import Jest Dom test utils.
import '#testing-library/jest-dom';
const localVue = createLocalVue();
Vue.use(Vuetify);
describe('Test Page', () => {
let vuetify;
beforeEach(() => {
vuetify = new Vuetify();
});
it('Check visibility of button', () => {
const wrapper = shallowMount(Test, {
localVue,
vuetify
});
const btn = wrapper.findComponent({ ref: 'btn' }).element;
expect(btn).toBeVisible();
});
it('Error Message hidden on page load', () => {
const wrapper = shallowMount(Test, {
localVue,
vuetify
});
const alert = wrapper.findComponent({ ref: 'error-msg' }).element;
expect(alert).not.toBeVisible();
});
});
// Package.json
"dependencies": {
"vue": "^2.6.11",
"vue-click-outside": "^1.1.0",
"vue-debounce": "^2.5.7",
"vue-router": "^3.3.4",
"vuetify": "^2.2.11",
"vuex": "^3.4.0"
},
"devDependencies": {
"#babel/plugin-transform-runtime": "^7.10.3",
"#babel/polyfill": "^7.10.1",
"#fortawesome/fontawesome-free": "^5.13.1",
"#testing-library/jest-dom": "^5.10.1",
"#vue/cli-plugin-babel": "^4.4.5",
"#vue/cli-plugin-e2e-nightwatch": "^4.4.5",
"#vue/cli-plugin-eslint": "^4.4.5",
"#vue/cli-plugin-unit-jest": "^4.4.5",
"#vue/cli-service": "^4.4.5",
"#vue/eslint-config-prettier": "^4.0.1",
"#vue/test-utils": "^1.0.3",
"babel-eslint": "^10.0.3",
"babel-jest": "^26.1.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^6.2.2",
"node-sass": "^4.14.1",
"sass": "^1.26.9",
"sass-loader": "^8.0.2",
"vue-cli-plugin-vuetify": "^2.0.6",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.5.0"
}
I ran into a similar issue, so I decided to use exists from #vue/test-utils instead.
Docs for exists: https://vue-test-utils.vuejs.org/api/wrapper/#exists
I also decided to use v-if (instead of v-model) on the v-alert element to hide / show the component.
It looks like if v-if receives a value of false, the component/element in the document is replaced with <!---->, which is great for checking if your component/element is hidden or displayed.
See v-if test spec: https://github.com/vuejs/vue/blob/52719ccab8fccffbdf497b96d3731dc86f04c1ce/test/unit/features/directives/if.spec.js
SFC
Template:
<template>
<v-container>
<v-btn
#click='showError()'
ref="showErrorButton">
Show Error
</v-btn>
<v-alert
v-if="errorEncountered"
ref="errorAlert"
colored-border
type="error"
elevation="2"
>
Oops! Something went wrong!
</v-alert>
<v-container>
<template>
Javascript:
export default {
methods: {
showError() {
this.errorEncountered = true;
}
}
data() {
return {
errorEncountered: false,
};
},
};
Whenever errorEncountered is updated, the v-alert component will show/hide depending on whether the value is true/false.
Tests
describe('Component', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(Component, {
localVue,
vuetify,
});
});
describe('When component is mounted', () => {
it('Then the default value for errorEncountered should be false', () => {
expect(wrapper.vm.errorEncountered).toBeFalse();
});
it('Then the default state for the error alert should be hidden', async () => {
const errorAlert = wrapper.find({ ref: 'errorAlert' });
expect(errorAlert.exists()).toBeFalse();
});
describe('When an error is encountered', () => {
it('Then errorEncountered should be set to true', async () => {
const showErrorButton = wrapper.find({ ref: 'showErrorButton' });
showErrorButton.trigger('click');
await Vue.nextTick();
expect(wrapper.vm.errorEncountered).toBeTrue();
});
it('Then error alert should be visible', async () => {
const showErrorButton = wrapper.find({ ref: 'showErrorButton' });
showErrorButton.trigger('click');
await Vue.nextTick();
const errorAlert = wrapper.find({ ref: 'errorAlert' });
expect(errorAlert.exists()).toBeTrue();
});
});
});

Quasar Unknown custom element error in unit test

I have a simple Vue component that uses Quasar button
<template>
<div>
<span class="count">{{ count }}</span>
<q-btn #click="increment">Increment</q-btn>
</div>
</template>
<script>
export default {
name: 'TestComponent',
data() {
return {
count: 0,
};
},
methods: {
increment() {
this.count += 1;
},
},
};
</script>
I create a unit test for it
import { mount, createLocalVue } from '#vue/test-utils';
import { Quasar, QBtn } from 'quasar';
import TestComponent from '../TestComponent';
describe('TestComponent', () => {
let wrapper;
beforeEach(() => {
const localVue = createLocalVue();
localVue.use(Quasar, { components: { QBtn } });
wrapper = mount(TestComponent, { localVue });
});
it('renders the correct markup', () => {
expect(wrapper.html()).toContain('<span class="count">0</span>');
});
// it's also easy to check for the existence of elements
it('has a button', () => {
expect(wrapper.contains('button')).toBe(true);
});
});
My problem:
If I run the test cases (it function) one by one at a time the test will pass. For example, remove the second it('has a button'...) then run the test. It'll pass. It's the same when removing the first it('renders the correct markup'...)
However, If I keep all test cases then run the test. The second test case will fail with an error
console.error node_modules/vue/dist/vue.common.dev.js:630
[Vue warn]: Unknown custom element: <q-btn> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
found in
---> <TestComponent>
<Root>
What am I doing wrong?
Try removing the before-each. I saw this problem too. Can't remember what exactly fixed it but this is how I have my describe block.
describe('Mount Quasar', () => {
const localVue = createLocalVue()
localVue.use(Quasar, { components })
const wrapper = shallowMount(Register, {
localVue,
stubs: ['router-link', 'router-view']
})
const vm = wrapper.vm
it('passes the sanity check and creates a wrapper', () => {
expect(wrapper.isVueInstance()).toBe(true)
})
})
You will need to import quasar into either webpack, babel, or jest.
In the jest.config.js file
Add
moduleNameMapper: {
quasar: "quasar-framework/dist/umd/quasar.mat.umd.min.js"
},

TestWindow is not a constructor in Stenciljs Unit test

Getting errors when I run unit test for the component
1) TestWindow is not a constructor
2) Cannot read property 'textContent' of undefined
Not able to understand how to proceed further. When I try to console element and testWindow, both are coming as undefined.
**tsx file**
import { Component } from '#stencil/core';
#Component({
tag: 'my-header',
styleUrl: 'my-header.css'
})
export class MyHeader {
render() {
return (
<div>
<p>Hello MyHeader!</p>
</div>
);
}
}
**Spec file**
import { TestWindow } from '#stencil/core/testing';
import { MyHeader } from './my-header';
describe('my-header', () => {
it('should build', () => {
expect(new MyHeader()).toBeTruthy();
});
describe('rendering', () => {
let element: HTMLMyHeaderElement;
let testWindow: TestWindow;
beforeEach(async () => {
testWindow = new TestWindow();
element = await testWindow.load({
components: [MyHeader],
html: '<my-header></my-header>'
});
});
console.log("element ",element);
console.log("testWindow ",testWindow);
it('should show content', () => {
expect(element.textContent).toEqual('');
});
});
});
package.json
"devDependencies": {
"#stencil/core": "~0.16.4",
"#stencil/sass": "^0.1.1",
"#types/jest": "23.3.11",
"#types/puppeteer": "1.6.4",
"jest": "^23.6.0",
"jest-cli": "23.6.0",
"puppeteer": "1.8.0",
"workbox-build": "3.4.1"
}
how can I get rid of those errors or I'm missing something to include.
The entire Stencil unit testing changed in latest versions.
TestWindow is now deprecated in favor of the combination of Jest and Puppeteer.
You should consult the documentation for further explanations about how to test your code: end to end testing in Stencil