javascriptvue.jsjestjsvue-jest

Using DOM directly in Vue unit test when no alternative


How do I correct my unit test to correctly test for the below?

Method:

close(event) {
  const element = !!event?.target?.closest('#target')

  if (!element) {
    this.isVisible = false
  }
},

Jest test:

  it('should hide visibility if element is false', () => {
    const wrapper = shallowMount(AccountLogin, { mocks })
    wrapper.vm.close()

    expect(wrapper.vm.$data.isVisible).toBe(false)
  })

Solution

  • If the change() event is fired on the root element of your component, this should work:

    jest.spyOn(wrapper, 'closest')
        .mockImplementationOnce(() => true)
    
    wrapper.vm.close()
    expect(wrapper.vm.$data.isVisible).toBe(false)
    

    If the event is triggered on a child of root component element, you'll need to pass that child to spyOn, so you mock that child's closest, instead of the wrappers. e.g:

    jest.spyOn(wrapper.find('input'), 'closest')
        .mockImplementationOnce(() => true)
    // ...
    

    Why you need to find the exact element: this is jsdom: it's not actual DOM. Events don't bubble.


    What the above does: it hijacks .closest() method of event.target's element so it returns true.
    Which, in your case, will cause

    !!event?.target?.closest('#target')
    

    to return true, so this.isVisible = true will be executed.

    If you want to test that it's not executed when #target is not found, write a second test, with a .closest() mock returning false and test that isVisible has not been set from false to true upon invoking .close(). Doing more than that would be testing that HTML works as expected.
    I recommended you trust that it does.