Why doesn't jest.spyOn() sometimes work on a Vue component's method?

独自空忆成欢 提交于 2021-01-29 14:18:02

问题


I see the changes the method makes as event handler, but jest.spyOn(wrapper.vm, 'methodName') doesn't catch the call in major cases, but in some tests it somehow works. I suspect a possible re-render (maybe, due to options argument in mount() call), that replaces the original method with the jest.spyOn(), because, if I trigger event twice in a row, toHaveBeenCalled() fulfills and tracks the call.

The component:

<template>
  <v-app-bar
    app
    color="primary"
    dark
    class="header"
  >
    <router-link
      :to="{name: 'User'}"
      class="up-and-down-link"
    >
      <v-toolbar-title>Тестовое задание</v-toolbar-title>
    </router-link>
    <v-spacer />
    <v-toolbar-items>
      <v-btn
        v-if="isLoggedIn"
        class="button-log-out"
        text
        @click="showLogOutModal"
      >
        Выйти из профиля
      </v-btn>
    </v-toolbar-items>
    <Modal
      v-bind="modal"
      @close="onModalClose"
      @click:outside="onModalClose(false)"
    >
      Вы уверены, что хотите выйти из профиля?
    </Modal>
  </v-app-bar>
</template>

<script>
import { LOG_OUT } from '@/store/auth/action-types';
import { IS_LOGGED_IN } from '@/store/auth/getter-types';
import Modal from '@/components/Modal.vue';

export default {
  name: 'Header',
  components: { Modal },
  data() {
    return {
      modal: {
        show: false,
        withConfirmation: true,
      },
    };
  },
  computed: {
    isLoggedIn() {
      return this.$store.getters[`auth/${IS_LOGGED_IN}`];
    },
  },
  methods: {
    showLogOutModal() {
      this.modal.show = true;
    },
    onModalClose(confirmation = false) {
      this.modal.show = false;
      if (confirmation === false) return;
      this.logOut();
    },
    logOut() {
      this.$store.dispatch(`auth/${LOG_OUT}`);
      this.$router.push({ name: 'Login' });
    },
  },
};
</script>

And that test works as expected:

it('After confirmation of the action user is logged out and redirected to login page', async () => {
    const actions = {
      [LOG_OUT]: jest.fn(),
    };
    await store.hotUpdate({ modules: { auth: { ...auth, actions } } });
    const mockedRouter = {
      push: jest.fn(),
    };
    const wrapper = createWrapper(Header, {
      data: () => ({ modal: { show: true } }),
      mocks: {
        $route: {},
        $router: mockedRouter,
      },
    });
    const mockedLogOutMethod = jest.spyOn(wrapper.vm, 'logOut');
    await wrapper.find('.button-yes').trigger('click');
    // the method's called
    expect(mockedLogOutMethod).toHaveBeenCalled();
    // the method performs log out and redirect
    expect(actions[LOG_OUT]).toHaveBeenCalled();
    expect(mockedRouter.push).toHaveBeenCalledWith({ name: 'Login' });
  });

But this one doesn't, though during it the data of the component is changed and we see it on the component's child prop ('show' turns to be true), thus the method is called indeed, but toHaveBeenCalled() can't detect the fact:

it("Show modal with confirmation when 'log out' button was clicked", async () => {
    const wrapper = createWrapper(Header);
    const mockedShowModalMethod = jest.spyOn(wrapper .vm, 'showLogOutModal');
    const modal = wrapper.findComponent(Modal);
    // the modal is hidden initially
    expect(modal.props('show')).toBe(false);
    await wrapper.find('.button-log-out').trigger('click');
    // after 'log out' button is clicked the modal appears
    expect(modal.props('show')).toBe(true);
    expect(mockedShowModalMethod).toHaveBeenCalled();
    expect(wrapper.find('.modal-confirmation').exists()).toBe(true);
  });

I found a workaround for that:

...    
const mockedShowModalMethod = jest.spyOn(Header.methods, 'showLogOutModal');
const wrapper = createWrapper(Header);
....

However, I wanna find out the reason of this behavior, what do I miss here? I have another test suites, where my initial assertion with jest.spyOn() works out as well as in the first test here.


回答1:


The difference between logOut and showLogOutModal methods is how they are used.

logOut is called as regular method:

this.logOut();

And showLogOutModal is passed as a callback:

  <v-btn
    v-if="isLoggedIn"
    class="button-log-out"
    text
    @click="showLogOutModal"

this.showLogOutModal is mocked with jest.spyOn after this.showLogOutModal is read to to be used as event handler, so a spy doesn't affect anything.

This would likely be fixed by forcing a re-render after replacing wrapper.vm.showLogOutModal with a spy:

const mockedShowModalMethod = jest.spyOn(wrapper.vm, 'showLogOutModal');
await wrapper.vm.$forceUpdate();

But a workaround listed in the question is the recommended way to do this:

const mockedShowModalMethod = jest.spyOn(Header.methods, 'showLogOutModal');
const wrapper = createWrapper(Header);


来源:https://stackoverflow.com/questions/61859423/why-doesnt-jest-spyon-sometimes-work-on-a-vue-components-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!