Search code examples
javascriptvue.jsvuejs2jestjsvue-test-utils

How do I mock a method that gets fired by a vue directive?


I'm trying to call the close() method in my Test component, but it only gets fired when clicking outside the div that the directive is on. What should I do in my test to make sure that that method gets fired? I'm using the v-click-outside npm package in my component.

Component

<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>

This is my test file.

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();
}

Solution

  • The directive is not setup properly in your component:

    import vClickOutside from 'v-click-outside'
    
    export default {
      directives: {
        // BEFORE: ❌ 
        vClickOutside,
    
        // AFTER: ✅
        clickOutside: vClickOutside.directive
      },
    }
    

    To verify that close() is called when you click outside the component:

    1. Mock the close method with jest.spyOn.
    2. Create a div for the test component, and attach the mounted wrapper to it.
    3. v-click-directive adds its event listeners on the next macro-tick (using setTimeout with no timeout), so the test also needs to wait a macro-tick for the directive to initialize.
    4. Trigger a click event on the wrapper, and await the result. Then, assert that close() was called.

    The test should look like this:

    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()
      }
    })