不使用Jest调用Vue / Vuex / Vuetify Actions模拟

时间:2019-04-11 18:53:04

标签: vue.js jestjs vuex vuetify.js vue-test-utils

我只是想测试Vuex存储操作是否已调度。

我尝试使用jest.spyOn来模拟https://vue-test-utils.vuejs.org/guides/#testing-vuex-in-components中所述的操作,在测试中创建一个异步函数,并按照vue unit test - data not updated after triggering event ( form submit) as expected中所述等待wrapper.vm。$ nextTick()并安装并导入How to test Vuex Mutations using Vue-test-utils and Jest

中所述的冲洗承诺

这些都不起作用。我什至在jest函数调用中放置了一个简单的console.log,以表明它实际上受到了攻击,但是我的期望值。toHaveBeenCalled()出现了错误。


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错误/警告,该错误/警告在与deepMount一起使用localVue时出现。也就是说,当我使用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可以清楚地看到模拟函数被击中(两次,出于某种奇怪的原因),但它并未注册它已被调用。

0 个答案:

没有答案