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
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);
});
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()
})
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()
})
})
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');
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.