Skip to main content

Vue Examples

info
What you'll learn
  • How to mount Vue components in Cypress
  • How to pass props and events to components
  • How to use slots in components
  • How to use Vue Test Utils with Cypress
  • How to customize cy.mount() with Vue

Mounting Components

Using cy.mount()

To mount a component with cy.mount(), import the component and pass it to the method:

import { Stepper } from './Stepper.vue'

it('mounts', () => {
cy.mount(Stepper)
})

Passing Data to a Component

You can pass props and events to a component by setting props in the options:

cy.mount(Stepper, {
props: {
initial: 100,
},
})

Testing Event Handlers

Pass a Cypress spy to an event prop and validate it was called:

it('clicking + fires a change event with the incremented value', () => {
const onChangeSpy = cy.spy().as('onChangeSpy')
cy.mount(Stepper, { props: { onChange: onChangeSpy } })
cy.get('[data-cy=increment]').click()
cy.get('@onChangeSpy').should('have.been.calledWith', 1)
})

Using JSX

The mount command also supports JSX syntax (provided that you've configured your bundler to support transpiling JSX or TSX files). Some might find using JSX syntax beneficial when writing tests.

Sample with JSX:

it('clicking + fires a change event with the incremented value', () => {
const onChangeSpy = cy.spy().as('onChangeSpy')
cy.mount(<Stepper initial={100} onChange={onChangeSpy} />)
cy.get('[data-cy=increment]').click()
cy.get('@onChangeSpy').should('have.been.calledWith', 101)
})

Using Slots

Default Slot

import DefaultSlot from './DefaultSlot.vue'

describe('<DefaultSlot />', () => {
it('renders', () => {
cy.mount(DefaultSlot, {
slots: {
default: 'Hello there!',
},
})
cy.get('div.content').should('have.text', 'Hello there!')
})
})

Named Slot

import NamedSlot from './NamedSlot.vue'

describe('<NamedSlot />', () => {
it('renders', () => {
const slots = {
header: 'my header',
footer: 'my footer',
}
cy.mount(NamedSlot, {
slots,
})
cy.get('header').should('have.text', 'my header')
cy.get('footer').should('have.text', 'my footer')
})
})

For more info on testing Vue components with slots, refer to the Vue Test Utils Slots guide.

Using Vue Test Utils

In order to encourage interoperability between your existing component tests and Cypress, we support using Vue Test Utils' API.

cy.mount(Stepper).then(({ wrapper, component }) => {
// `wrapper` is the Vue Test Utils wrapper
// `component` is the component instance itself
})

If you intend to use the wrapper frequently and use Vue Test Util's API, we recommend you write a custom mount command and create a Cypress alias to get back at the wrapper.

import { mount } from 'cypress/vue'

Cypress.Commands.add('mount', (...args) => {
return mount(...args).then(({ wrapper }) => {
return cy.wrap(wrapper).as('vue')
})
})

// the "@vue" alias will now work anywhere
// after you've mounted your component
cy.mount(Stepper).doStuff().get('@vue') // The subject is now the Vue Wrapper

This means that you are able to get to the resulting wrapper returned from the mount command and use wrapper.emitted() in order to gain access to Native DOM events that were fired, as well as custom events that were emitted by your component under test.

Because wrapper.emitted() is only data, and NOT spy-based you will have to unpack its results to write assertions.

Your test failure messages will not be as helpful because you're not able to use the Sinon-Chai library that Cypress ships, which comes with methods such as to.have.been.called and to.have.been.calledWith.

Usage of the cy.get('@vue') alias may look something like the below code snippet.

Notice that we're using the 'should' function signature in order to take advantage of Cypress's retryability. If we chained using cy.then instead of cy.should, we may run into the kinds of issues you have in Vue Test Utils tests where you have to use await frequently in order to make sure the DOM has updated or any reactive events have fired.

cy.mount(Stepper, { props: { initial: 100 } })
cy.get(incrementSelector).click()
cy.get('@vue').should(({ wrapper }) => {
expect(wrapper.emitted('change')).to.have.length
expect(wrapper.emitted('change')[0][0]).to.equal('101')
})

Regardless of our recommendation to use spies instead of the internal Vue Test Utils API, you may decide to continue using emitted as it automatically records every single event emitted from the component, and so you won't have to create a spy for every event emitted.

This auto-spying behavior could be useful for components that emit many custom events.

Custom Mount Commands

Customizing cy.mount()

While you can use the mount() function in your tests, we recommend using cy.mount(), which is a custom command that is defined in the cypress/support/component.js file:

import { mount } from 'cypress/vue'

Cypress.Commands.add('mount', mount)

This allows you to use cy.mount() in any test without having to import the mount() function in each and every spec file.

By default, cy.mount() is a simple passthrough to mount(), however, you can customize cy.mount() to fit your needs. For instance, if you are using plugins or other global app-level setups in your Vue app, you can configure them here.

Below are a few examples that demonstrate using a custom mount command. These examples can be adjusted for most other providers that you will need to support.

Replicating Plugins

Most applications will have state management or routing. Both of these are Vue plugins.

import { createPinia } from 'pinia' // or Vuex
import { createI18n } from 'vue-i18n'
import { mount } from 'cypress/vue'
import { h } from 'vue'

// We recommend that you pull this out
// into a constants file that you share with
// your main.js file.
const i18nOptions = {
locale: 'en',
messages: {
en: {
hello: 'hello!',
},
ja: {
hello: 'こんにちは!',
},
},
}

Cypress.Commands.add('mount', (component, ...args) => {
args.global = args.global || {}
args.global.plugins = args.global.plugins || []
args.global.plugins.push(createPinia())
args.global.plugins.push(createI18n())

return mount(
() => {
return h(VApp, {}, component)
},
...args
)
})

Replicating the expected Component Hierarchy

Some Vue applications, most famously Vue apps built on top of Vuetify, require certain components to be structured in a specific hierarchy.

All Vuetify applications require that you wrap your app in a VApp component when you build it. This is an implementation detail of Vuetify, but once users try to test components that depend on Vuetify, they get Vuetify-specific compilation errors and quickly find out that they need to replicate that component hierarchy any time they need to mount a component that uses a Vuetify component!

Custom cy.mount commands to the rescue! You may find the JSX syntax to be more straightforward.

You'll also need to replicate the plugin setup steps from the Vuetify docs for everything to compile.

import Vuetify from 'vuetify/lib'
import { VApp } from 'vuetify'
import { mount } from 'cypress/vue'
import { h } from 'vue'

// We recommend that you pull this out
// into a constants file that you share with
// your main.js file.
const vuetifyOptions = {}

Cypress.Commands.add('mount', (component, ...args) => {
args.global = args.global || {}
args.global.plugins = args.global.plugins || []
args.global.plugins.push(new Vuetify(vuetifyOptions))

return mount(
() => {
return h(VApp, {}, component)
},
...args
)
})

Vue Router

To use Vue Router, create a command to register the plugin and pass in a custom implementation of the router via the options param.

Vue 3

import { mount } from 'cypress/vue'
import { createMemoryHistory, createRouter } from 'vue-router'
import { routes } from '../../src/router'

Cypress.Commands.add('mount', (component, options = {}) => {
// Setup options object
options.global = options.global || {}
options.global.plugins = options.global.plugins || []

// create router if one is not provided
if (!options.router) {
options.router = createRouter({
routes: routes,
history: createMemoryHistory(),
})
}

// Add router plugin
options.global.plugins.push({
install(app) {
app.use(options.router)
},
})

return mount(component, options)
})

Vue 2

import { mount } from 'cypress/vue'
import Vue from 'vue'
import VueRouter from 'vue-router'
import { router } from '../../src/router'

Cypress.Commands.add('mount', (component, options = {}) => {
// Add the VueRouter plugin
Vue.use(VueRouter)

// Use the router passed in via options,
// or the default one if not provided
options.router = options.router || router

return mount(component, options)
})

Vuex

To use a component that uses Vuex, create a mount command that configures a Vuex store for your component.

Vue 3

import { mount } from 'cypress/vue'
import { getStore } from '../../src/plugins/store'

Cypress.Commands.add('mount', (component, options = {}) => {
// Setup options object
options.global = options.global || {}
options.global.stubs = options.global.stubs || {}
options.global.stubs['transition'] = false
options.global.components = options.global.components || {}
options.global.plugins = options.global.plugins || []

// Use store passed in from options, or initialize a new one
const { store = getStore(), ...mountOptions } = options

// Add Vuex plugin
options.global.plugins.push({
install(app) {
app.use(store)
},
})

return mount(component, mountOptions)
})
info

The getStore method is a factory method that initializes Vuex and creates a new store. It is important that the store be initialized with each new test to ensure changes to the store don't affect other tests.

Vue 2

import { mount } from 'cypress/vue'
import Vuex from 'vuex'
import { getStore } from '../../src/plugins/store'

Cypress.Commands.add('mount', (component, options = {}) => {
// Setup options object
options.extensions = options.extensions || {}
options.extensions.plugins = options.extensions.plugins || []

// Use store passed in from options, or initialize a new one
options.store = options.store || getStore()

// Add Vuex plugin
options.extensions.plugins.push(Vuex)

return mount(component, options)
})
info

The getStore method is a factory method that initializes Vuex and creates a new store. It is important that the store be initialized with each new test to ensure changes to the store don't affect other tests.

Global Components

If you have components that are registered globally in the main application file, set them up in your mount command so your component will render them properly:

import { mount } from 'cypress/vue'
import Button from '../../src/components/Button.vue'

Cypress.Commands.add('mount', (component, options = {}) => {
// Setup options object
options.global = options.global || {}
options.global.components = options.global.components || {}

// Register global components
options.global.components['Button'] = Button

return mount(component, options)
})