VueJS test-utils 无法在子组件中找到元素

VueJS test-utils can't find element inside child component

我正在尝试使用 findComponentfind 方法来查找子组件的元素并设置它的值。但是每次我 运行 测试时,它都会给我 Cannot call setValue on an empty DOMWrapper. 错误。

Test file

import { mount } from '@vue/test-utils';
import Create from './Create.vue';
// import State from '@/components/State.vue';

describe('it tests Create component', () => {
    test('it emits create event and resets the form when form is valid and create button is clicked', async () => {
        const div = document.createElement('div');
        div.id = 'root';
        document.body.append(div);

        const expectedNameValue = 'TODO_NAME';
        const expectedStateValue = 'Pending';

        const wrapper = mount(Create, {
            attachTo: '#root',
        });

        await wrapper.find(`input`).setValue(expectedNameValue);
        await wrapper.findComponent({ ref: 'state-component' }).find('select').setValue(expectedStateValue);        
        
        await wrapper.find(`form`).trigger('submit');

        expect(wrapper.emitted().create).toBeTruthy();
        expect(wrapper.emitted().create[0]).toEqual([expectedNameValue]);
        expect(wrapper.emitted().create[1]).toEqual(['Pending']);
        expect(wrapper.find(`input[name='name']`).element.value).toEqual('');
        expect(wrapper.find(`input[name='state']`).element.value).toEqual('Pending');
    });
});

Create component

<template>
    <form @submit.prevent="createTodo" class="flex gap-2 w-full">
        <input class="flex-1 shadow rounded-md p-2 focus:ring-2 focus:ring-blue-900 focus:outline-none" type="text" placeholder="Todo Name" name="name" required/>
        <State ref="state-component"/>
        <button type="submit" class="rounded-md shadow text-white bg-blue-700 py-2 px-6">Create</button>
    </form>
</template>

<script>
import State from '@/components/State.vue';

export default {
    components: { State },
    emits: ['create'],
    methods: {
        createTodo(event) {
            const elems = event.target.elements;
            const todo = { name: elems.name.value, state: elems.state.value };
            this.$emit('create', todo);

            elems.name.value = '';
            elems.state.value = 'Pending';
        }
    }
}
</script>

<style scoped>
</style>

State component

<template>
  <select id="state-select" class="rounded-md bg-green-200 text-white" name="state">
    <option
      v-for="(state, index) in states"
      :selected="isSelected(state)"
      :key="index"
    >
      {{ state }}
    </option>
  </select>
</template>

<script>
export default {
  props: ["todo", "index"],
  data() {
    return {
      name: "",
      state: "",
      states: ["Pending", "In Progress", "Done"],
    };
  },
  created() {
    if(!this.todo) return true;

    this.state = this.todo.state;
    this.name = this.todo.name;
  },
  methods: {
    isSelected(equivalent){
      return equivalent === this.state;
    }
  }
};
</script>

<style scoped></style>

我是 VueJS 的新手,所以我愿意接受所有提示和技巧,谢谢。

需要解决的一些问题:

  1. 您不需要将组件附加到文档,因此删除:

    // ❌
    // const div = document.createElement('div');
    // div.id = 'root';
    // document.body.append(div);
    // const wrapper = mount(Create, { attachTo: '#root' });
    
    // ✅
    const wrapper = mount(Create);
    
  2. State 组件的模板引用将是组件的根元素,因此不需要 find() <select>:

    // ❌
    // await wrapper.findComponent({ ref: 'state-component' }).find('select').setValue(expectedStateValue);
                                                              ^^^^^^^^^^^^^^^
    // ✅
    await wrapper.findComponent({ ref: 'state-component' }).setValue(expectedStateValue);
    
  3. emitted()对象键是事件名称,值是数组的数组,包含发出的数据。您可以验证第一个 create 事件数据包含另一个对象 toMatchObject(object):

    // ❌
    // expect(wrapper.emitted().create[0]).toEqual([expectedNameValue]);
    // expect(wrapper.emitted().create[1]).toEqual(['Pending']);
    
    // ✅
    expect(wrapper.emitted().create[0][0]).toMatchObject({ name: expectedNameValue, state: 'Pending' });
    
  4. 最后一个断言试图找到 input[name='state'],但那实际上是 <select>,而不是 <input>:

    // ❌
    // expect(wrapper.find(`input[name='state']`).element.value).toEqual('Pending')
                            ^^^^^
    // ✅
    expect(wrapper.find(`select[name='state']`).element.value).toEqual('Pending')
    

demo