Недавно я изучал Vue.js, создавая небольшое приложение. Часть моего приложения отображает информацию с помощью таблицы, поэтому я выделил строку в отдельный функциональный компонент с помощью функции рендеринга, которая возвращает несколько корневых узлов. Это отлично сработало, за исключением тех случаев, когда пришло время протестировать компонент.

<!-- Cell.vue -->
<script>
export default {
  functional: true,
  props: ['cellData'],
  render: function (h, context) {
    return [
      h('td', context.props.cellData.category),
      h('td', context.props.cellData.description)
    ]
  }
}
</script>

Первой сложной частью было то, что я обнаружил, что вы не можете использовать propsData для передачи реквизита функциональному компоненту. Вместо этого вам нужно передать объект контекста, который имеет свойство props.

Затем я попытался shallowMount компонент, как я обычно делаю, но это привело к предупреждению: [Vue warn]: Multiple root nodes returned from render function. Render function should return a single root node.

// Cell.spec.js
import { shallowMount } from '@vue/test-utils'
import Cell from '@/components/Cell'

wrapper = shallowMount(Cell, {
  context: {
    props: {
      cellData {
        category: 'foo',
        description: 'bar'
      }
    }
  }
}); // [Vue warn]: Multiple root nodes returned from render function. Render function should return a single root node.

Затем я попытался обернуть компонент в один <div>, чтобы создать один корневой узел. Это тоже не сработало, так как я получил ошибку: [vue-test-utils]: mount.context can only be used when mounting a functional component.

// Cell.spec.js
import { shallowMount } from '@vue/test-utils'
import Cell from '@/components/Cell'

wrapper = shallowMount('<div><Cell></div>', {
  context: {
    props: {
      cellData {
        category: 'foo',
        description: 'bar'
      }
    }
  }
}); // [vue-test-utils]: mount.context can only be used when mounting a functional component.

В этот момент я не знал, что делать так (так как передать propsData тоже не получалось), поэтому обратился в StackOverflow и спросил там.

Получается, что нужно создать компонент-обертку, который отрисовывает функциональный компонент и пробрасывает ему все пропсы и слушатели. Затем вы можете mount компонент-оболочку протестировать функциональный компонент.

// Cell.spec.js
import { mount } from '@vue/test-utils'
import Cell from '@/components/Cell'

const WrappedCell = {
  components: { Cell },
  template: `
    <div>
      <Cell v-bind="$attrs" v-on="$listeners" />
    </div>
  `
}

const wrapper = mount(WrappedCell, {
  propsData: {
    cellData: {
      category: 'foo',
      description: 'bar'
    }
  }
});

describe('Cell.vue', () => {
  it('should output two tds with category and description', () => {
    expect(wrapper.findAll('td')).toHaveLength(2);
    expect(wrapper.findAll('td').at(0).text()).toBe('foo');
    expect(wrapper.findAll('td').at(1).text()).toBe('bar');
  });
});

Последней частью головоломки было выяснить, как проверить, что родительский компонент, использующий функциональный компонент, передает ему правильные реквизиты. Обычно вы используете wrapper.props() для проверки реквизита, но это не работает для функционального компонента, так как вы получаете сообщение об ошибке: [vue-test-utils]: wrapper.props() cannot be called on a mounted functional component.

Вместо этого, как я понял, вы можете использовать метод, аналогичный тестированию функционального компонента. При монтировании компонента вы можете передать ему опцию заглушки, в которой вы можете заменить один компонент на другой. Это позволяет нам создать компонент-оболочку и заглушить его для функционального компонента.

Поскольку компонент-оболочка не работает, вы можете вызвать для него props(), чтобы проверить, что мы передаем ему правильные данные. Компоненту-оболочке не нужно отображать функциональный компонент или даже регистрировать его как component. Единственное требование состоит в том, что он объявляет тот же props, что и функциональный компонент.

// ParentCell.spec.js
import { shallowMount } from '@vue/test-utils'
import ParentCell from '@/components/ParentCell'

const Cell = {
  props: ['cellData'],
  template: '<div></div>'
}

describe('ParentCell.vue', () => {

  let wrapper;
  beforeEach(() => {
    wrapper = shallowMount(ParentCell, {
      stubs: {
        Cell: Cell
      }
    });
  });

  it('should correctly render the cell', () => {
    expect(wrapper.find(Cell).exists()).toBe(true);
    expect(wrapper.find(Cell).props('cellData')).toEqual({
      category: 'foo',
      description: 'bar'
    });
  });
});

Первоначально опубликовано на stevenklambert.com 10 февраля 2019 г.