Vue + Vuetify - test:unit cannot see the v-alert message value - unit-testing

In my component template I have a v-alert vuetify sub-component
<v-alert dismissible #input="closeAlert()" #type="msgTypeContactForm" v-model="msgStatusContactForm">{{ msgValueContactForm }}</v-alert>
using the following data
data() {
return {
...
msgStatusContactForm: false,
msgTypeContactForm: "",
msgValueContactForm: ""
};
},
on submitting the form, when an error is raised , I set these data
catch (err) {
this.msgTypeContactForm = "error";
this.msgValueContactForm = this.$i18n.t("lang.views.home.contactForm.post_error");
this.msgStatusContactForm = true;
This is running fine, the alert is correctly displayed with the correct type and value ..
However in the component unit test , the alert properties and value are not updated in the template
it("should not sendMessage - invalid form", async () => {
...
wrapper = mount(ContactForm, options);
const contactForm = wrapper.find("form");
...
const btnSubmit = wrapper.find("#btnSubmit");
btnSubmit.trigger("click");
await wrapper.vm.$nextTick();
// then
setTimeout(() => {
expect(wrapper.vm.validForm).toEqual(false);
expect(wrapper.vm.msgStatusContactForm).toEqual(true);
expect(wrapper.vm.msgTypeContactForm).toEqual("error");
}, 2000);
await wrapper.vm.$nextTick();
const alert = wrapper.find(".v-alert");
console.log("INVALID FORM ALERT: ", alert.html());
})
console.log tests/unit/ContactForm.spec.js:383
INVALID FORM ALERT: cancel
the alert should be displayed and the type set and message value present in the html output ....
I don't know where I am wrong in my test ? any help appreciated

SOLVED..
It's a matter of handling async/await function... in my component
submitForm: async function() {
....
So I use flush-promises in my test
yarn add -D flush-promises
then
import flushPromises from "flush-promises";
...
it("should not sendMessage - invalid form", async () => {
...
wrapper = mount(ContactForm, options);
...
// then
const btnSubmit = wrapper.find("#btnSubmit");
btnSubmit.trigger("click");
await flushPromises();
...
const alert = wrapper.find(".v-alert");
console.log("INVALID FORM ALERT: ", alert.html());
Then I can see the DOM updated
<div class="v-alert error" style=""><div>One or more fields are invalid. Please, review your input and submit</div><a class="v-alert__dismissible"><i aria-hidden="true" class="v-icon v-icon--right material-icons theme--light">cancel</i></a></div>

Related

vue-test-utils find did not return , cannot call setValue()

I have a textbox inside a model. what I want is to test the model by various functions. at the moment what the error occurs is [vue-test-utils]: find did not return #txtForget, cannot call setValue() on empty Wrapper
Login.vue has a textbox / input box -> I have used vuetify
<v-row class="ma-0 pa-0 mt-5 MultiFormStyle ">
<!-- EMAIL -->
<v-col md="12" sm="12" cols="12" class="">
<ValidationProvider
rules="required|email"
name="EMAIL ADDRESS"
v-slot="{ errors }"
>
<v-text-field
v-model="editedItem.email"
:label="errors[0] ? errors[0] : 'EMAIL ADDRESS'"
:error-messages="errors"
dense
hide-details=""
id="txtForget"
>
</v-text-field>
</ValidationProvider>
</v-col>
</v-row>
</ValidationObserver>
Login.spec.js has a test as follows
test("RESET PASSWORD test", async () => {
let wrapper = mount(Login, {
stubs: ["router-link", "router-view"],
vuetify,
router,
localVue,
});
wrapper.vm.editedItem.email = "admin#gmail.com";
let element_textbox = wrapper.find("#txtForget");
await element_textbox.setValue("test#gmail.com");
expect(wrapper.vm.editedItem.email).toBe("admin#gmail.com");
});
i found the issue and solved it as follows
checking if the model exist
let ForgetModel = wrapper.find("#forgetModel");
expect(ForgetModel.exists()).toBe(true);
then triging the button to open the model
let ForgetPasswordBtn = wrapper.find("button#forgotPasswordBtn");
ForgetPasswordBtn.trigger("click");
await wrapper.vm.$nextTick();
after that find the input element and write a text on it
let element_email = wrapper.find("#txtForget");
await element_email.setValue("test#gmail.com");
finally checking the written value is bonded or not
expect(wrapper.vm.editedItem).toBe("test#gmail.com");
this is the proper method I found from various articles that works on vuetify.
complete code is below
test("RESET PASSWORD test", async () => {
let wrapper = mount(Login, {
stubs: ["router-link", "router-view"],
vuetify,
router,
localVue,
});
let ForgetModel = wrapper.find("#forgetModel");
let ForgetPasswordBtn = wrapper.find("button#forgotPasswordBtn");
ForgetPasswordBtn.trigger("click");
await wrapper.vm.$nextTick();
let element_email = wrapper.find("#txtForget");
await element_email.setValue("test#gmail.com");
expect(ForgetModel.exists()).toBe(true);
expect(wrapper.vm.editedItem).toBe(true);
});

vue unit test - data not updated after triggering event ( form submit) as expected

I am testing that form data are sent after submit.
ContactForm.spec.js
import Vue from "vue";
import Vuex from "vuex";
import { mount, shallowMount } from "#vue/test-utils";
import VeeValidate from "vee-validate";
import i18n from "#/locales";
import Vuetify from "vuetify";
import ContactForm from "#/components/Home/ContactForm.vue";
Vue.use(VeeValidate, { errorBagName: "errors" });
Vue.use(Vuetify);
Vue.use(Vuex);
describe("ContactForm.vue", () => {
let store;
let wrapper;
let options;
let input;
const v = new VeeValidate.Validator();
beforeEach(() => {
const el = document.createElement("div");
el.setAttribute("data-app", true);
document.body.appendChild(el);
});
it("should sendMessage - valid form", async () => {
// given
store = new Vuex.Store({
state: {
language: "en",
loading: false
}
})
options = {
sync: false,
provide: {
$validator () {
return new VeeValidate.Validator();
}
},
i18n,
store
};
wrapper = mount(ContactForm, options);
// when
const radioInput = wrapper.findAll('input[type="radio"]');
radioInput.at(1).setChecked(); // input element value is changed, v-model is not
radioInput.at(1).trigger("change"); // v-model updated
input = wrapper.find('input[name="givenName"]');
input.element.value = "John"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
input = wrapper.find('input[name="familyName"]');
input.element.value = "Doe"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
input = wrapper.find('input[name="email"]');
input.element.value = "john.doe#example.com"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
input = wrapper.find('textarea[name="messageContent"]');
input.element.value = "Hello World!"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
const contactForm = wrapper.find("form");
contactForm.trigger("submit");
await wrapper.vm.$nextTick();
// then
console.log("DATA: ", wrapper.vm.$data.contactLang);
expect(wrapper.vm.validForm).toEqual(true);
});
});
Validation is successful ( so validForm is set to true in the component )
BUT the test does not pass
console.log
✕ should sendMessage - valid form (476ms)
● ContactForm.vue › should sendMessage - valid form
expect(received).toEqual(expected)
Expected value to equal:
true
Received:
false
The component vue is
ContactForm.vue
<template>
<form id="contactForm" #submit="sendMessage()">
<input v-model="contactLang" type='hidden' data-vv-name="contactLang" v-validate="'required'" name='contactLang'>
<v-layout row wrap align-center>
<v-flex xs12 sm3 md3 lg3>
<v-radio-group row :mandatory="false" v-model="gender" name="gender">
<v-radio :label='genderLabel("f")' value="f" name="female"></v-radio>
<v-radio :label='genderLabel("m")' value="m" name="male"></v-radio>
</v-radio-group>
</v-flex>
<v-flex xs12 sm4 md4 lg4>
<v-text-field
v-model="givenName"
browser-autocomplete="off"
:label="$t('lang.views.home.contactForm.givenName')"
data-vv-name="givenName"
:error-messages="errors.collect('givenName')"
v-validate="'required'"
name="givenName">
</v-text-field>
</v-flex>
<v-flex xs12 sm5 md5 lg5>
<v-text-field
v-model="familyName"
browser-autocomplete="off"
:label="$t('lang.views.home.contactForm.familyName')"
data-vv-name="familyName"
:error-messages="errors.collect('familyName')"
v-validate="'required'"
name="familyName">
</v-text-field>
</v-flex>
</v-layout>
<v-text-field
browser-autocomplete="off"
v-model="email"
:label="$t('lang.views.home.contactForm.email')"
data-vv-name="email"
:error-messages="errors.collect('email')"
v-validate="'required|email'"
name="email">
</v-text-field>
<v-textarea v-model="messageContent" :label="$t('lang.views.home.contactForm.message')" :error-messages="errors.collect('messageContent')" :rules="[(v) => v.length <= 200 || 'Max 200 characters']" :counter="200" v-validate="'required'" data-vv-name="messageContent" name="messageContent"></v-textarea>
<v-btn id="btnClear" round #click.native="clear">{{ $t('lang.views.global.clear') }}</v-btn>
<v-btn round large color="primary" type="submit">{{ $t('lang.views.global.send') }}
<v-icon right>email</v-icon><span slot="loader" class="custom-loader"><v-icon light>cached</v-icon></span>
</v-btn>
</form>
</template>
<script>
import swal from "sweetalert2";
import { mapState } from "vuex";
import appValidationDictionarySetup from "#/locales/appValidationDictionary";
export default {
name: "contactForm",
$_veeValidate: { validator: "new" },
data() {
return {
contactLang: "",
gender: "f",
givenName: "",
familyName: "",
email: "",
messageContent: "",
validForm: false
};
},
...
methods: {
...
sendMessage: function() {
console.log("sendMessage()...");
this.$validator
.validateAll()
.then(isValid => {
console.log("VALIDATION RESULT: ", isValid);
this.validForm = isValid;
if (!isValid) {
console.log("VALIDATION ERROR");
// console.log("Errors: ", this.$validator.errors.items.length);
const alertTitle = this.$validator.dictionary.container[
this.language
].custom.error;
const textMsg = this.$validator.dictionary.container[this.language]
.custom.correct_all;
swal({
title: alertTitle,
text: textMsg,
type: "error",
confirmButtonText: "OK"
});
return;
}
console.log("validation success, form submitted validForm: ", this.validForm);
return;
})
.catch(e => {
// catch error from validateAll() promise
console.log("error validation promise: ", e);
this.validForm = false;
return;
});
},
clear: function() {
this.contactLang = "";
this.gender = "f";
this.givenName = "";
this.familyName = "";
this.email = "";
this.messageContent = "";
this.validForm = false;
this.$validator.reset();
}
},
mounted() {
appValidationDictionarySetup(this.$validator);
this.$validator.localize(this.language);
this.contactLang = this.language;
}
};
</script>
</style>
and the console.log debugging is
console.log src/components/Home/ContactForm.vue:90
sendMessage()...
console.log tests/unit/ContactForm.spec.js:242
DATA: en
console.log src/components/Home/ContactForm.vue:94
VALIDATION RESULT: true
It's weird to see that the DATA ( contactLang ) value is false in the console.log from the spec ... displayed before the validation result
console.log src/components/Home/ContactForm.vue:90
sendMessage()...
console.log tests/unit/ContactForm.spec.js:242
DATA: en
console.log src/components/Home/ContactForm.vue:94
VALIDATION RESULT: true
console.log src/components/Home/ContactForm.vue:112
validation success, form submitted validForm: true
I guess there is async problem ... timout ?
thanks for feedback
SOLVED
It's actually a timeout issue
expect.assertions(1); // Otherwise jest will give a false positive
await contactForm.trigger("submit");
// then
setTimeout(() => {
expect(wrapper.vm.validForm).toEqual(true);
}, 2000);
I propose to use jest faketimers
jest.useFakeTimers()
contactForm.trigger("submit");
await wrapper.vm.$nextTick();
// then
jest.runTimersToTime(2000)
expect(wrapper.vm.validForm).toEqual(true);
I suggest to first make the test fail to avoid false positives
for more information about jest faketimers
https://jestjs.io/docs/en/timer-mocks.html
i did a simple test for my login form of component, in case it helps someone
it("submit form call method login", () => {
const login = jest.fn()
const wrapper = shallowMount(Login, {
methods: {
login
}
})
wrapper.findAll("v-form").at(0).trigger("submit")
expect(login).toBeCalled()
})

Vue Test Utils / Jest - How to test if class method was called within a component method

I have an interesting problem with a unit test of mine. My unit test is written to click on a button inside a component. This button calls a component method which contains an instance of a class Service (a wrapper class for axios). The only thing this component method does is call Service.requestPasswordReset(). My unit test needs to verify that Service.requestPasswordReset was called.
I know I'm mocking my Service class correctly, because this passes in my unit test:
await Service.requestPasswordReset()
expect(Service.requestPasswordReset).toHaveBeenCalled()
And I know that I'm calling the method correctly on click because this passes in my unit test:
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
I just can't get my test to register that the Service method gets called. Any ideas?
Component
<template lang="pug">
Layout
section
header( class="text-center py-4 pb-12")
h1( class="text-grey-boulder font-light mb-4") Recovery Email
p( class="text-orange-yellow") A recovery email has been sent to your email address
div( class="text-center")
div( class="mb-6")
button(
type="button"
#click.stop="resend()"
class="bg-orange-coral font-bold text-white py-3 px-8 rounded-full w-48"
) Resend Email
</template>
<script>
import Layout from '#/layouts/MyLayout'
import Service from '#/someDir/Service'
export default {
name: 'RecoveryEmailSent',
page: {
title: 'Recovery Email Sent',
},
components: {
Layout,
},
data() {
return {
errorMessage: null
}
},
computed: {
userEmail() {
const reg = this.$store.getters['registration']
return reg ? reg.email : null
},
},
methods: {
async resend() {
try {
await Service.requestPasswordReset({
email: this.userEmail,
})
} catch (error) {
this.errorMessage = error
}
},
},
}
</script>
Service.js
import client from '#/clientDir/BaseClient'
class Service {
constructor() {
this.client = client(baseUrl)
}
requestPasswordReset(request) {
return this.client.post('/account_management/request_password_reset', request)
}
}
export { Service }
export default new Service()
Service.js in __mock__
export default {
requestPasswordReset: jest.fn(request => {
return new Promise((resolve, reject) =>
resolve({
data: {
statusCode: 'Success',
},
})
)
})
}
Unit Test
jest.mock('#/someDir/Service')
import { shallowMount, mount, createLocalVue } from '#vue/test-utils'
import RecoveryEmailSent from './AccountManagement.RecoveryEmailSent'
import Service from '#/someDir/Service'
const localVue = createLocalVue()
// localVue.use(Service) // <-- Tried this, didn't work
describe('Recovery Email Sent', () => {
it('should resend recovery email', async () => {
const mockMethods = {
resend: jest.fn()
}
const email = 'testemail#test.com'
const wrapper = mount(RecoveryEmailSent, {
localVue,
computed: {
userEmail() {
return email
},
},
methods: mockMethods
})
// await Service.requestPasswordReset()
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
expect(Service.requestPasswordReset).toHaveBeenCalled()
})
})
I figured it out. Apparently, Jest's .toHaveBeenCalled() doesn't return true if the method in question was called with parameters. You MUST use .toHaveBeenCalledWith(). I don't see anything about this caveat in their docs, but it does seem to be the case.
Here is my passing test code
it('should resend email hash', async () => {
const email = 'testemail#test.com'
const wrapper = mount(AccountManagementForgottenPasswordSubmitted, {
localVue,
computed: {
userEmail() {
return email
},
},
})
await wrapper.find('button').trigger('click')
expect(Service.requestPasswordReset).toHaveBeenCalledWith({
email: email
})
})
You can use inject-loader to mock your Service
Basic idea:
const RecoveryEmailSentInjector = require('!!vue-loader?inject!./AccountManagement.RecoveryEmailSent')
import Service from '#/someDir/Service'
const mockedServices = {
'#/someDir/Service': Service
}
describe('Recovery Email Sent', () => {
it('should resend recovery email', async () => {
const RecoveryEmailSentWithMocks = RecoveryEmailSentInjector(mockedServices)
const wrapper = mount(RecoveryEmailSentWithMocks, {
...
})
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
expect(mockedServices.requestPasswordReset).toHaveBeenCalled()
})
})

Test state value after promise resolved

Given an component that make a login ...
onSubmit(e) {
e.preventDefault();
const { userStore, data } = this.props;
this.setState({isLogin: true});
const promise = userStore.login(data);
promise.then(() => {
this.setState({isLogin: false});
})
}
How can I create a test that validate if isLogin state is false after the promise is resolved?
So far, I have ..
it('resolved submit should set isLogin to false', () => {
mockApi.onPost('/authenticate').reply(200);
const userStore = new UserStore();
let fromPromiseLogin;
userStore.login = jest.fn()
.mockImplementationOnce((data) => {
fromPromiseLogin = userStore.login(data);
return fromPromiseLogin;
});
const wrapper = mount(
<FormLogin userStore={userStore} />,
);
wrapper.find('form').simulate('submit');
// HOW CAN I WAIT/OR CALLBACK FOR THE PROMISE ? I TRIED >
whenWithTimeout(
() => fromPromiseLogin && fromPromiseLogin.state !== PENDING,
() => expect(wrapper.state().isLogin).toBeFalsy(),
);
});
But a exeption
[mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: 'Reaction[When#21] TypeError: Cannot read property 'state' of undefined
EDIT:
This answer is no longer relevant after the question has been edited.
Have you tried to test the result of the promise with .resolves function as described here?
expect(myPromise).resolves.toEqual('SomeValue');
Or using async/await:
const result = await myPromise;
expect(result).toEqual('SomeValue');

Mocha - how to check if element exist after ajax call

How to check if select have some option in it after it has been updated by ajax function. My select component is like below
class Select extends React.component(){
constructor(){
super()
this.state.item=[]
}
componentWillMount(){
axios.get(list)
.then(function (response) {
this.setState({item:response.data})
// reponse data ['america','singapore','vietnam']
})
}
render(){
return(
<select>
this.state.item.map((i) => {
<option value="i">i</option>
})
</select>
)
}
}
Here is my try:
import {expect} from 'chai'
import {shallow} from 'enzyme'
import sinon from 'sinon'
import Select from 'Select'
describe('select list', () => {
it('should have options', () => {
const wrapper = shallow(<Select />)
wrapper.update()
const actual = wrapper.find('option').length
expect(actual).to.not.equal(0)
})
})
What wrong with this is, I got var actual = 0. It supposes to be 3. So I guess I missing something with axios. What should I add to my spec?
Your GET request might be still waiting for the response but mocha already has completed the test execution.
You could add a timeout and assert after a while and then call the done() callback when you are done with the test. Please take a look at the mocha's asynchronous code section.
describe('select list', () => {
it('should have options', (done) => {
const wrapper = shallow(<Select />)
wrapper.update()
setTimeout(() => {
const actual = wrapper.find('option').length
expect(actual).to.not.equal(0)
done();
}, 2000)
})
})
I recommend you to check the axios-mock-adapter which allows you to mock request rather than sending an actual request to the server.