Я просто пытаюсь проверить, было ли отправлено действие хранилища Vuex или нет.
Я попытался использовать jest.spyOn, высмеивая действие, как описано в https://vue -test-utils.vuejs.org/guides/#testing-vuex-in-components, создающий асинхронную функцию в тесте и ожидающий wrapper.vm. $ nextTick (), как описано в vue unit test - данные не обновляютсяпосле запуска события (отправка формы), как ожидалось и установки и импорта flush-обещаний, как описано в Как проверить мутации Vuex с помощью Vue-test-utils и Jest
Ни одно из этихработал.Я даже поместил простой console.log в вызове функции jest, чтобы показать, что он действительно получает удар, но мой wait.toHaveBeenCalled () выдает false.
ChangePassword.vue
<template>
<v-card>
<v-card-title>
<p class="title">Change your password</p>
</v-card-title>
<v-form @submit="submitPasswordChange" onsubmit="return false;">
<v-card-text>
<p class="subheading">User: {{ email }}</p>
<v-text-field
v-model="oldPassword"
:append-icon="show_old ? 'visibility_off' : 'visibility'"
:type="show_old ? 'text' : 'password'"
name="oldPassword"
:error-messages="oldPasswordErrors"
label="Current Password"
@click:append="show_old = !show_old"
jest="old"
required
@input="$v.oldPassword.$touch()"
@blur="$v.oldPassword.$touch()"
></v-text-field>
<v-text-field
v-model="newPassword1"
:append-icon="show_new1 ? 'visibility_off' : 'visibility'"
:type="show_new1 ? 'text' : 'password'"
name="newPassword1"
:error-messages="newPassword1Errors"
label="Password"
hint="At least 6 characters"
counter
@click:append="show_new1 = !show_new1"
jest="new1"
></v-text-field>
<v-text-field
v-model="newPassword2"
:append-icon="show_new2 ? 'visibility_off' : 'visibility'"
:type="show_new2 ? 'text' : 'password'"
name="newPassword2"
:error-messages="newPassword2Errors"
label="Re-enter Password"
hint="At least 6 characters"
counter
@click:append="show_new2 = !show_new2"
@submit=""
@keyup.enter.native="submitPasswordChange"
jest="new2"
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
small
color="blue"
@click="submitPasswordChange"
class="white--text"
jest="button"
>
Change Password
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</template>
<script>
import { mapState } from 'vuex';
import { validationMixin } from 'vuelidate';
import { required, sameAs, minLength } from 'vuelidate/lib/validators';
export default {
name: "ChangePassword",
mixins: [validationMixin],
validations: {
oldPassword: { required },
newPassword1: { required, sameAsPassword: sameAs('newPassword2'), minLength: minLength(6) },
newPassword2: { required, sameAsPassword: sameAs('newPassword1'), minLength: minLength(6) }
},
data() {
return {
oldPassword: null,
newPassword1: null,
newPassword2: null,
show_old: false,
show_new1: false,
show_new2: false,
}
},
computed: {
...mapState({
email: state => state.user.email,
}),
oldPasswordErrors() {
const errors = [];
if (!this.$v.oldPassword.$dirty) return errors;
!this.$v.oldPassword.required && errors.push('Old password is required.');
return errors
},
newPassword1Errors() {
const errors = [];
if (!this.$v.newPassword1.$dirty) return errors;
!this.$v.newPassword1.required && errors.push('New password is required.');
!this.$v.newPassword1.minLength && errors.push('Password must be at least 6 characters.')
return errors
},
newPassword2Errors() {
const errors = [];
if (!this.$v.newPassword2.$dirty) return errors;
!this.$v.newPassword2.required && errors.push('Please repeat the new password.');
!this.$v.newPassword2.minLength && errors.push('Password must be at least 6 characters.')
!this.$v.newPassword2.sameAsPassword && errors.push('Passwords must be equal.');
return errors
}
},
methods: {
async submitPasswordChange() {
this.$v.$touch();
if (!this.$v.$invalid) {
try {
const response = await this.$store.dispatch('user/changePassword', {
oldPassword: this.oldPassword,
newPassword1: this.newPassword1,
newPassword2: this.newPassword2,
});
this.$v.reset();
}
catch(err) {
let message = '';
Object.keys(err.response.data).map(key => {
err.response.data[key].map(errorMsg => {
message += `${errorMsg} `
})
});
}
}
},
},
}
</script>
<style scoped>
</style>
store -> user.js module
import DjangoAPI from '@/services/api/DjangoService'
const state = {
email: '',
};
export const actions = {
async changePassword({}, payload) {
try {
const response = await DjangoAPI.changePassword(payload);
return Promise.resolve(response);
} catch(err) {
console.log('err in store.user/changePassword', err);
return Promise.reject(err);
}
},
}
export default {
state,
actions,
namespaced: true
}
ChangePassword.spec.js
import Vue from 'vue';
import { mount, createLocalVue } from "@vue/test-utils";
import Vuex from 'vuex';
import Vuetify from 'vuetify';
import ChangePassword from '@/components/user/ChangePassword';
Vue.use(Vuex);
Vue.use(Vuetify);
describe('ChangePassword', () => {
let store, actions;
beforeEach(() => {
actions = {
changePassword: jest.fn(console.log('abcdefg')),
};
console.warn = jest.fn();
console.error = jest.fn();
store = new Vuex.Store({
modules: {
user: {
state: {email: 'test@email.com'},
actions,
},
}
})
});
it('mounts properly', () => {
mount(ChangePassword, {store});
expect(true).toBe(true);
});
it('submits change password properly', async() => {
// const dispatchSpy = jest.spyOn(store, 'dispatch');
const wrapper = mount(ChangePassword, {store});
const old = wrapper.find('[jest="old"]');
const new1 = wrapper.find('[jest="new1"]');
const new2 = wrapper.find('[jest="new2"]');
const button = wrapper.find('[jest="button"]');
old.element.value = 'old_password';
new1.element.value = 'new_password';
new2.element.value = 'new_password';
button.trigger('click');
expect(actions.changePassword).toHaveBeenCalled();
// expect(dispatchSpy).toHaveBeenCalled();
})
});
Несколько заметок.Я использую mount вместо shallowMount и Vue вместо localVue просто для того, чтобы справиться с ошибками / предупреждениями Vuetify, которые появляются при использовании localVue с shallowMount.Тем не менее, я все еще получаю ту же ошибку при тестировании, когда я использую shallowMount и localVue.
Это ошибка, которую я получаю при использовании localVue и shallowMount.
FAIL tests/unit/components/user/ChangePassword.spec.js
● Console
console.error node_modules/vuetify/dist/vuetify.js:23011
[Vuetify] Multiple instances of Vue detected
See https://github.com/vuetifyjs/vuetify/issues/4068
If you're seeing "$attrs is readonly", it's caused by this
console.log tests/unit/components/user/ChangePassword.spec.js:18
abcdefg
console.log tests/unit/components/user/ChangePassword.spec.js:18
abcdefg
● ChangePassword › submits change password properly
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called, but it was not called.
47 | button.trigger('click');
48 |
> 49 | expect(actions.changePassword).toHaveBeenCalled();
| ^
50 | // expect(dispatchSpy).toHaveBeenCalled();
51 | })
52 | });
at Object.toHaveBeenCalled (tests/unit/components/user/ChangePassword.spec.js:49:36)
at tryCatch (node_modules/regenerator-runtime/runtime.js:65:40)
at Generator.invoke [as _invoke] (node_modules/regenerator-runtime/runtime.js:303:22)
at Generator.prototype.(anonymous function) [as next] (node_modules/regenerator-runtime/runtime.js:117:21)
at asyncGeneratorStep (node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:5:24)
at _next (node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:27:9)
at node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:34:7
at new F (node_modules/core-js/library/modules/_export.js:36:28)
at Object.<anonymous> (node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:23:12)
И при использовании mount и«полный» экземпляр Vue, который я получаю так:
FAIL tests/unit/components/user/ChangePassword.spec.js
● Console
console.log tests/unit/components/user/ChangePassword.spec.js:16
abcdefg
console.log tests/unit/components/user/ChangePassword.spec.js:16
abcdefg
● ChangePassword › submits change password properly
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called, but it was not called.
45 | button.trigger('click');
46 |
> 47 | expect(actions.changePassword).toHaveBeenCalled();
| ^
48 | // expect(dispatchSpy).toHaveBeenCalled();
49 | })
50 | });
at Object.toHaveBeenCalled (tests/unit/components/user/ChangePassword.spec.js:47:36)
at tryCatch (node_modules/regenerator-runtime/runtime.js:65:40)
at Generator.invoke [as _invoke] (node_modules/regenerator-runtime/runtime.js:303:22)
at Generator.prototype.(anonymous function) [as next] (node_modules/regenerator-runtime/runtime.js:117:21)
at asyncGeneratorStep (node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:5:24)
at _next (node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:27:9)
at node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:34:7
at new F (node_modules/core-js/library/modules/_export.js:36:28)
at Object.<anonymous> (node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:23:12)
Из console.log ясно, что запускается фиктивная функция (дважды, по какой-то странной причине), но она не регистрирует, что она быланазывается.