如何模拟被 vue 指令触发的方法?

Posted

技术标签:

【中文标题】如何模拟被 vue 指令触发的方法?【英文标题】:How do I mock a method that gets fired by a vue directive? 【发布时间】:2021-02-24 15:21:12 【问题描述】:

我试图在我的Test 组件中调用close() 方法,但只有在该指令启用的div 外部单击时才会触发它。我应该在测试中做些什么来确保该方法被触发?我在我的组件中使用v-click-outside npm 包。

组件

<script>
  import vClickOutside from 'v-click-outside';

  export default 
    name: 'Test',

    directives: 
      vClickOutside,
    ,

    data: () => (
      isOpen: false,
    ),
    methods: 
      close() 
        this.isOpen = false;
      ,
  ;
</script>

<template>
  <div
    v-click-outside="close"
    class="test-class"
  >
    <OtherComponent />
  </div>
</template>

这是我的测试文件。

const clickOutsidelDirective = jest.fn();

describe('Test.vue', () => 
  const wrapper = shallowMount(Component, 
   directives: 
      clickOutside: clickOutsidelDirective,
    ,
  );
   wrapper.find('.test-class').trigger('click');
   //not sure what i have to do to mock the close() function 

   //This doesn't get called
   expect(clickOutsidelDirective).toHaveBeenCalled();

【问题讨论】:

【参考方案1】:

在您的主要组件中,该指令是显式导入的。因此,在您的测试中,您不需要再次定义它。 v-click-outside 有一个后果,你应该测试它。这意味着, close 方法应该触发,而不是模拟整个指令。类似的东西:

编辑: 你的定义包括the directive is wrong:

<template>
  <div>
    <div
        v-click-outside="close"
        class="test-class"
    >
      <h1>H1</h1>
    </div>
    <div>
      <h1 class="outside-class">Outside</h1>
    </div>
  </div>
</template>
<script>
import clickOutside from 'v-click-outside';

export default 
  name: 'Test',

  directives: 
    clickOutside: clickOutside.directive,
  ,

  data() 
    return 
      isOpen: false,
    ;
  ,
  methods: 
    close() 
      this.isOpen = true;
    ,
  

</script>

通过新版本的 Vue-test-utils,方法覆盖将被弃用,所以,这样的事情应该可以工作:

const wrapper = shallowMount(HelloWorld)
wrapper.find('.test-class').trigger('click')
expect(wrapper.vm.isOpen).toBeTruthy()
wrapper.find('.outside-class').trigger('click')
expect(wrapper.vm.isOpen).toBeFalsy()

但事实并非如此。它与内部的 v-click-outside 实现有关。我认为指令和shallowMount有问题。

【讨论】:

似乎即使这样做也不会触发方法/指令被调用 我已经在浏览器中测试了这个模板,一切都很顺利。但是在单元测试中没有用。它应该与指令工作的环境有关。【参考方案2】:

该指令未在您的组件中正确设置:

import vClickOutside from 'v-click-outside'

export default 
  directives: 
    // BEFORE: ❌ 
    vClickOutside,

    // AFTER: ✅
    clickOutside: vClickOutside.directive
  ,

验证close() 在组件外部单击时是否被调用:

    Mock the close method 和 jest.spyOn。 为测试组件创建一个div,并为它创建一个已安装的包装器attach。 v-click-directive adds its event listeners on the next macro-tick (using setTimeout with no timeout),所以测试还需要等待一个宏滴答来初始化指令。 在包装器上触发click 事件,并在结果中触发await。然后,断言 close() 被调用。

测试应该如下所示:

it('click directive', async () => 
  1️⃣
  const closeFn = jest.spyOn(HelloWorld.methods, 'close')

  2️⃣ 
  const div = document.createElement('div')
  document.body.appendChild(div)

  const wrapper = mount(
    template: `<div><HelloWorld /></div>`,
    components: 
      HelloWorld
    ,
  ,  attachTo: div )

  try 
    3️⃣
    await new Promise(r => setTimeout(r))

    4️⃣
    await wrapper.trigger('click')
    expect(closeFn).toHaveBeenCalled() ✅

   finally 
    wrapper.destroy()
  
)

【讨论】:

@FirzokNadeem 这个特定指令的实现检查元素外部的点击,因此我们将该元素包装在可以在测试中点击的div 中。目标元素需要附加到 div 才能使测试正常工作。

以上是关于如何模拟被 vue 指令触发的方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何测试在悬停/鼠标悬停时调用了 Vue 指令?

前端笔记八vue指令的事件修饰符

前端笔记八vue指令的事件修饰符

Vue之指令

使用带有 select2 指令的 vue.js 方法

vue.bind啥时候触发