How do you unit test a Vue.js functional component with a render function that returns any array of elements?

Viewed 3749

In Vue.js, a functional component can return multiple root nodes by using a render function that returns an array of createdElements.

export default {
  functional: true,
  props: ['cellData'],
  render: function (h, context) {
    return [
      h('td', context.props.cellData.category),
      h('td', context.props.cellData.description)
    ]
  }
}

This works great but I'm having trouble trying to create a unit test for such a component. Using shallowMount on the component results in [Vue warn]: Multiple root nodes returned from render function. Render function should return a single root node.

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

wrapper = shallowMount(Cell, {
  context: {
    props: {
      cellData {
        category: 'foo',
        description: 'bar'
      }
    }
  }
});

This github issue suggests that the component needs to be wrapped in a single root node to actually render it, but trying that results in [vue-test-utils]: mount.context can only be used when mounting a functional component

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

wrapper = shallowMount('<div><Cell></div>', {
  context: {
    props: {
      cellData {
        category: 'foo',
        description: 'bar'
      }
    }
  }
});

So how do I test a functional component that returns multiple root nodes?

2 Answers

You could create a higher order, transparent wrapper component that passes all props and event listeners to the inner Cell component using v-bind="$attrs"[1] and v-on="$listeners"[2]. Then you can use propsData to pass props to the wrapper component ..

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'
    }
  }
});

You can create a fragment_wrapper for wrapping your Components with Fragments (multiple root elements).

//File: fragment_wrapper.js

exports.fragment_wrapper = function(FragmentComponent){
  const wrapper = {
    components: { FragmentComponent },
    props: FragmentComponent.props,
    template: `<div><FragmentComponent v-bind="$props" v-on="$listeners"/></div>`
  }
  return wrapper;  
}

Then you can use this to test all your Fragmented Components as follows:

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


describe('Test Cell', () => {
  let WrappedCell = fragment_wrapper(Cell);
  const wrapper = mount(WrappedCell, {
    propsData: {
      cellData: {
        category: 'foo',
        description: 'bar'
      }
    }
  });

  it('renders the correct markup', () => {
    expect(wrapper.html()).toContain('<td>foo</td>')
  });
});
Related