Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vue jest spyOn not working when calculating watcher method calls

I'm getting familiar with jest and vue and I wanted to see how to make sure a method fired when a prop changed. In this particular scenario it's trivial and this seemed straight forward. But it's not working.

Components Watcher

@Watch("id")
    public async idChanged() {
        this.calculateStatus();
    }

beforeEach - this initilizes the wrapper for each test

beforeEach(async () => {
        var httpClient = new PemHttpClient(vue);
        var v3ReferenceDatumService = new V3ReferenceDatumService(httpClient, "");
        var contractService = new V3ContractService(httpClient, "", v3ReferenceDatumService);

        wrapper = mount(AmendmentIdDisplay, {
            provide: {
                v3ContractService: contractService,
            },
            propsData: {
                id: "82.5.1"
            }
        });

        await wrapper.vm.$nextTick();
    })

Jest Test

        let calculateFired = jest.spyOn(wrapper.vm, "calculateStatus");

        wrapper.setProps({
            ...wrapper.props(),
            id: "1"
        })

        await wrapper.vm.$nextTick();

        expect(calculateFired).toBeCalled();

I would expect the spy to have incremented the call counter but it does not. It remains at zero. If I manually call wrapper.vm.calculateStatus(), the spy works correctly. So the setProps is either not firing the watcher at all, or some weird reference thing is happening which is causing the method that is called within the watcher, to not be the method I'm spying on. I'm not sure which.

like image 622
cphilpot Avatar asked Mar 16 '20 20:03

cphilpot


1 Answers

I hope it is not too late. Yes there is a problem with jest.spyOn() and vue watchers. I have a trick that patch the problem for now (tested on sync function only) :

const insertSpyWatcher = (vueInstance: any, watcherExpression: string, spyInstance: jest.SpyInstance) => {
  let oldWatcherIndex = -1;
  let deep = false; // pass the deep option value from the original watcher to the spyInstance

  // find the corresponding watcher
  vueInstance._watchers.forEach((watcher: any, index: number) => {
    if (watcher.expression === watcherExpression) {
      oldWatcherIndex = index;
      deep = watcher.deep;
    }
  });

  // remove the existing watcher
  if (oldWatcherIndex >= 0) {
    vueInstance._watchers.splice(oldWatcherIndex, 1);
  } else {
    throw new Error(`No watchers found with name ${watcherExpression}`);
  }

  // replace it with our watcher
  const unwatch = vueInstance.$watch(watcherExpression, spyInstance, { deep });
  return unwatch;
};

Then in your test :

it('test the watcher call', () => {
  let calculateFired = jest.spyOn(wrapper.vm, "calculateStatus");
  insertSpyWatcher(wrapper.vm, "id", calculateFired) // Yes 'id' is the name of the watched property
  wrapper.setProps({
    ...wrapper.props(),
    id: "1"
  })
  await wrapper.vm.$nextTick();
  expect(calculateFired).toBeCalled();
});

If the immmediate property is needed, you can always add it as argument of insertSpyWatcher. I did not find a way to get the immediateproperty of the original watcher.

like image 195
Lyokolux Avatar answered Nov 09 '22 03:11

Lyokolux