Test on function call in Vue template only passes if the function is called with parentheses

这一生的挚爱 提交于 2020-12-12 11:28:32

问题


I am using Vue v2.6 with Jest (v24.9) and Vue-Test-Utils (v1.03).

In order to mock a method I have seen two different syntaxes,

wrapper.vm.updateCart = jest.fn();

and

wrapper.setMethods({ updateCart: jest.fn() });

but the first is not documented, while the second is deprecated (see docs).

The problem is that with both these methods, the only way to make a test pass is to call the method with the parentheses inside the template, which is weird because all the docs I've read somehow encourage to use methods without parentheses in the templates.

So this test:

test('Click on .btn calls the right function', () => {
    const wrapper = shallowMount(Modal, {
        propsData: {
            validate: jest.fn(),
        },
    });
    wrapper.setMethods({ updateCart: jest.fn() });
    const $btn = wrapper.find('.btn');
    $btn.trigger('click');
    expect(wrapper.vm.updateCart).toHaveBeenCalled();
});

will only pass if I call the method with parentheses:

<button class="btn" @click="updateCart()">
  {{ dictionary('remove') }}
</button>

but will fail otherwise (eg. using @click="updateCart").

Which is the right syntax to test that an event in a template is calling a function?

And why the deprecation warning in the docs (here and here) define the setMethod api as an anti-pattern?

Maybe it is just wrong to only test that an event in a template triggers a function because this behavior should already be guaranteed by the framework (Vue) and instead we should only focus on testing the function itself?

EDIT 07/02/2020

I have also tried a different syntax:

const spy = jest.spyOn(wrapper.vm, 'updateCart');
const $btn = wrapper.find('.btn');
$btn.trigger('click');
expect(spy).toHaveBeenCalled();

which prevents from overwriting the method and replaces the call to setMethods, but still the test only passes when the function is called with parentheses.


回答1:


Both @click="updateCart" and @click="updateCart()" syntax variations are supported, which is confusing because Vue DSL allows to provide expressions in v-on. The former uses updateCart method as event handler, while the latter evaluates the expression against component instance and isn't limited to methods.

@click="updateCart()" is preferable for consistency reasons because expressions are commonly used in Vue templates, also @click="notAFunction" silently fails while @click="notAFunction()" throws an error. This also allows to spy on it with:

jest.spyOn(wrapper.vm, 'updateCart');

Otherwise the method needs to be spied before component instantiation:

jest.spyOn(Modal.methods, 'updateCart');
const wrapper = shallowMount(Modal);

The deprecation of setMethod has been explained:

There's no clear path to replace setMethods, because it really depends on your previous usage. It easily leads to flaky tests that rely on implementation details, which is discouraged .

...

To stub a complex method extract it from the component and test it in isolation. To assert that a method is called, use your test runner to spy on it.

The concerns about testing the implementation are subjective because it's often not a bad thing. It depends on the case whether the extraction is practical, because a function needs to be extracted to another module in order to be spyable.

As for updateCart, it may be enough to mock underlying API that updates the carе, not the method itself.

It still may be useful to test click event with stubbed method, or at least spy on real method. It's the template that is tested this way, not just the behaviour of the framework. Without a spy, it's unknown whether the test fails because of btn or updateCart.



来源:https://stackoverflow.com/questions/62696939/test-on-function-call-in-vue-template-only-passes-if-the-function-is-called-with

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