Vue / Vuex / Vuetify Actions макет не вызывается с помощью Jest - PullRequest
0 голосов
/ 11 апреля 2019

Я просто пытаюсь проверить, было ли отправлено действие хранилища 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 ясно, что запускается фиктивная функция (дважды, по какой-то странной причине), но она не регистрирует, что она быланазывается.

...