Vue how to test component with slot and slot-props Vue how to test component with slot and slot-props vue.js vue.js

Vue how to test component with slot and slot-props


I'm a bit late to the party but I just hit the same problem. I took a few cues from the actual Vue tests themselves, while they're more abstract in what they're testing compared to us, it did help.

Here's what I came up with:

import { shallowMount } from '@vue/test-utils';import Component from 'xyz/Component';let wrapperSlotted;describe('xyz/Component.vue', () => {  beforeAll(() => {    wrapperSlotted = shallowMount(Component, {      data() {        return { myProp: 'small' }      },      scopedSlots: {        default: function (props) {          return this.$createElement('div', [props.myProp]);        }      },    });  });  it('slot prop passed to a scoped slot', () => {    let text = wrapperSlotted.find('div').text();    expect(text).toBe('small'); // the value of `myProp`, which has been set as the text node in the slotted <div />  });});

So the main thing was that I used the render function for scopedSlots.

Hope that helps someone :)


UPD: below the line is the original answer that I gave years ago. For today I would say that one of these approaches would be good enough:

  1. Don't test the slot method, but test the user-facing feature that relies on it. Try to test it manually first, notice how do you do it, then try to write a similar test. E.g. with testing-library/vue

  2. If the first option is too hard to do, try to come up with some fake testing component. The idea is very similar to what I described in the question

But that feels like tests for FooComponent inside tests for ParentComponent

But instead of using the ParentComponent just create some very simple inline component (right in your FooComponent.spec.js file), that uses the component with a slot.


Original answer that was given in 2020:

Since there are no answers so I just share what I ended up with.

I decided to test the internal method. It's not cool, but at least, because I use typescript, I have a type-safe test, which will fail with a clear type error if I rename or modify the method which I test. Looks like this:

import Foo from '@/components/foo/Foo.ts'import FooComponent from '@/components/foo/Foo.vue'/*...*/cosnt wrapper = <Foo>shallowMount(FooComponent, /* ... */)// notice that because I passed `Foo` which is a class-component, // I have autocomplete and type checks for vm.*wrapper.vm.internalFn(/* test payload */)expect(wrapper.emitted()).toBe(/* some expectation */)


After some experimentation, I've found a way to test functions passed in the scoped slot.

// MyComponent.vue<template>  <div>    <slot :data="{ isLoading, response, reload: fetchData }" />  </div></template><script>export default {  name: 'MyComponent',  data() {    return {      isLoading: false,      response: null,    }  },  created() {    this.fetchData()  },  methods: {    async fetchData() {      this.isLoading = true      // Some async action...      this.response = 'Hello Word'      this.isLoading = false    }  }}</script>// MyComponent.spec.js// I'm cutting some corners here, you should mock your async implementation using Jest (jest.fn() or jest.mock())// I recommend using `flushPromises` package to flush async requests//...it('should fetch data again', async () => {  const wrapper = shallowMount(MyComponent, {    scopedSlots: {      default: `        <test @click="props.data.reload">Click here</test>      `,    },    components: {      test: Vue.component('test', {        template: `<div><slot /></div>`      })    },  })  await wrapper.vm.$nextTick()  // await flushPromises()  /* Pre-assertion */  expect(myMock).toHaveBeenCalledTimes(1)  wrapper.find('test-stub').vm.$emit('click')  await wrapper.vm.$nextTick()  // await flushPromises()  /* Assertion */  expect(myMock).toHaveBeenCalledTimes(2)})