skip to content

With Vue Test Utils' mount(), how do you pass props, slots and attrs to a Vue component, and change its props later?

level: juniorimportance: must knowfreq 58%

answer

  1. second argument to mount()
  2. three keys, three kinds of input
  3. slot value: string, h() vnode or component
  4. same key in props and attrs
  5. setProps lives on the root wrapper only

basics

~20 s

Pass an options object as mount()'s second argument: props for declared props, slots keyed by slot name (a string, an h() vnode or a component) and attrs for undeclared attributes. Change props later with await wrapper.setProps({...}) on the root wrapper.

solid answer

~50 s

`mount(Component, options)` takes a mounting-options object. `props` supplies declared props. `slots` maps slot names to content — `default`, a named key such as `footer`, or a scoped slot — given as a string template, an `h()` vnode, a function or an imported component. `attrs` supplies attributes the component does not declare, which by default fall through to its root element. Vue Test Utils actually merges `attrs` and `props` into one vnode props object (props last), so Vue itself decides what is a prop, and a key present in both resolves to the `props` value. After mounting, `wrapper.setProps({ title: 'New' })` merges new values into the root's props and returns a promise you await before asserting. It works only on the wrapper `mount()` returned; calling it on a `findComponent()` child throws `You can only use setProps on your mounted component`.

code

ts · 23 lines
ts
import { mount } from '@vue/test-utils'
import { h } from 'vue'
import InvoiceCard from './InvoiceCard.vue'
import StatusBadge from './StatusBadge.vue'

test('renders props, slots and fallthrough attrs', async () => {
  const wrapper = mount(InvoiceCard, {
    props: { title: 'Invoice #42', total: 120 },
    slots: {
      default: 'Line items go here',
      footer: h('button', { type: 'button' }, 'Pay now'),
      badge: StatusBadge
    },
    attrs: { id: 'invoice-42', 'data-test': 'card' }
  })

  expect(wrapper.get('h2').text()).toBe('Invoice #42')
  expect(wrapper.attributes('id')).toBe('invoice-42')

  await wrapper.setProps({ title: 'Invoice #43' })
  expect(wrapper.get('h2').text()).toBe('Invoice #43')
  expect(wrapper.props('total')).toBe(120)
})

go deeper

for a junior

Recall the three mounting options by name and what each feeds: props for declared inputs, slots by slot name, attrs for everything undeclared. Know that setProps is awaited and belongs to the root wrapper.

for a middle

Explain that mount builds a wrapper parent which renders your component from one merged props object, so Vue decides the prop-or-attr split, and that setProps merges into that reactive object.

for a senior

Use mounting options to express a realistic parent: real slot content, realistic attrs for accessibility hooks, and prop changes driven through setProps or interaction rather than by reaching into child instances.

for a principal

Set suite conventions so mounting mirrors real usage: shared factory helpers that build props and slots, no direct instance mutation, and review rules that reject setProps workarounds on children.

## What mount() is and what its options are for **Vue Test Utils** (the `@vue/test-utils` package, version 2.x for Vue 3) renders a component into a detached DOM and hands back a **`VueWrapper`** — an object with methods such as `find()`, `text()`, `props()`, `emitted()` and `setProps()`. Its entry point is `mount(Component, options)`. The second argument is the **mounting options** object, and three of its keys describe what a parent would normally hand the component: | option | what it feeds | typical value | |---|---|---| | `props` | props the component declares with `defineProps` or `props:` | `{ title: 'Invoice #42', total: 120 }` | | `slots` | the component's `<slot>` outlets | `{ default: 'text', footer: h('button', 'Pay') }` | | `attrs` | anything the component does **not** declare | `{ id: 'card-1', 'data-test': 'card' }` | Other options exist (`global`, `shallow`, `attachTo`, `data`), but these three are how you set up the component's inputs. ## props: the declared inputs `props` is a plain object of prop values. Under the hood Vue Test Utils creates a small wrapper parent that renders your component with these values, so the component receives them exactly as it would from a real parent: types are validated, defaults apply to missing keys and `required` props warn when absent. - Pass real values, including objects and functions; they are not stringified. - The older Vue Test Utils 1 option name `propsData` is still accepted for backward compatibility, but `props` is the current name. ## slots: default, named and scoped The `slots` object is keyed by slot name. `default` fills the unnamed `<slot />`; any other key fills the matching `<slot name="...">`. Each value can take several forms: - **a string** — treated as a template, so `'<strong>Hi</strong>'` renders an element, not escaped text; - **an `h()` vnode** — `h('button', { type: 'button' }, 'Pay now')`; - **a component** — an imported SFC is rendered in the slot; - **a function** — called with the slot's props, returning vnodes. For a **scoped slot**, a string can declare the scope explicitly with `<template #row="scope">{{ scope.item.name }}</template>`; if you omit the wrapping `<template>`, Vue Test Utils exposes the slot scope as an object named `params`. ## attrs: everything the component does not declare `attrs` is for **fallthrough attributes**: `id`, `class`, `aria-*`, `data-*` or listeners the component does not list among its props or emits. By default Vue applies them to the component's single root element, so `wrapper.attributes('id')` reads them back. Mechanically, `mount()` spreads `attrs` and then `props` into one vnode props object. That has two consequences worth knowing: 1. When a key appears in both, the `props` value wins, because it is spread last; `wrapper.props()` shows it and `wrapper.attributes()` does not. 2. The option names record your intent, but the prop-or-attribute split is made by Vue from the component's declarations, exactly as in a real parent. ## Changing props after mount: setProps `wrapper.setProps({ title: 'Invoice #43' })` writes the new values into the reactive props object the wrapper parent renders from, so the component re-renders as if its parent had changed a binding. Keys you do not mention keep their current values — it merges, it does not replace. It returns a promise; await it before asserting on the DOM. `setProps` exists only on the wrapper `mount()` returned. A child wrapper from `findComponent()` gets its props from its real parent's render, which the test does not control, so calling `setProps` there throws `You can only use setProps on your mounted component`. To change a child's props, change whatever feeds them: the root's props, or an interaction that updates the parent's state. ```ts const wrapper = mount(InvoiceCard, { props: { title: 'Invoice #42', total: 120 }, slots: { footer: '<button>Pay now</button>' }, attrs: { 'data-test': 'card' } }) await wrapper.setProps({ total: 99 }) // title keeps 'Invoice #42' ``` ## Common mistakes - Passing a slot's content through `props` because the child renders it — slot content goes in `slots`. - Expecting a string slot to render as literal text: it is compiled as a template. - Calling `setProps` on a child wrapper and treating the thrown error as a Vue bug. - Forgetting that `setProps` merges, then asserting that an omitted prop fell back to its default.

  • How do you fill a scoped slot when mounting with Vue Test Utils, and read the slot's scope?
    Give the slot a string such as `<template #row="scope">{{ scope.item.name }}</template>`, which names the scope explicitly. Without the wrapping `<template>`, Vue Test Utils exposes the scope as `params`, so `'{{ params.item.name }}'` works too. A function value is another option: it receives the slot props and returns vnodes built with `h()`.
  • A child's rendering depends on a prop its parent passes; how do you exercise a new value without setProps on the child?
    Change the source of that prop. If the parent derives it from its own props, call `setProps` on the root wrapper; if it comes from parent state, drive the interaction that changes that state, such as `trigger('click')`. The child's props are its parent's render output, which is exactly what the test should be exercising.

saying these in an interview costs you the question

  • Slot content should be passed as a prop because the child renders it.
  • A string slot value is always rendered as escaped plain text.
  • setProps replaces the whole props object, resetting keys you leave out.
  • You can call setProps on any wrapper returned by findComponent().
  • When a key is in both attrs and props, the attrs value wins.