Vue Components with Props

Mounting Your First Component

Simple components with few environmental dependencies will be easiest to test.

Start with a <Button>, a <Stepper>, or the <HelloWorld> component that comes with the initial Vue project scaffold. Here are a few examples of similar components that will be easier or harder to test.

Easier-to-testHarder-to-test
A Presentational ComponentA Presentational Component w/ Vuetify
Layout Component w/ SlotsLayout Component w/ Vue Router
Product List with PropsProduct List w/ Pinia or Vuex

In this guide, we'll use a <Stepper/> component with zero dependencies and one bit of internal state -- a simple "counter" that can be incremented and decremented by two buttons.

We'll add one prop, called initial to set the initial value in the <Stepper>.

Then, we'll emit a custom "change" event whenever the user clicks on the increment and decrement buttons. Testing this is covered in "Testing Emitted Events".

We'll start simple and build out our Stepper as we go.

it('renders the Stepper, with a default of 0', () => {
  cy.mount(Stepper)
})
// JSX for Vue components? Yes, and for good reason!
// See the section on JSX in Vue below
it('renders the Stepper, with a default of 0', () => {
  cy.mount(() => <Stepper />)
})
<template>
  <div data-testid="stepper">
    <button aria-label="decrement" @click="count--">-</button>
    {{ count }}
    <button aria-label="increment" @click="count++">+</button>
  </div>
</template>

<script>
  export default {
    data: () => ({
      count: 0,
    }),
  }
</script>

Testing a Component with Props

We're going to add functionality to our Stepper.vue component.

We want to be able to control the initial value of the stepper. To do this, we'll declare initial as an optional prop and test that the prop is used by the component.

To test this component, we'll exercise the Stepper's API and validate that the initial prop sets the internal count state for the first time.

it('supports an "initial" prop to set the value', () => {
  cy.mount(Stepper, { props: { initial: 100 } })
  cy.get('[data-testid=stepper]').should('contain.text', 100)
})
it('supports an "initial" prop to set the value', () => {
  cy.mount(() => <Stepper initial={100} />)
  cy.get('[data-testid=stepper]').should('contain.text', 100)
})
<template>
  <div data-testid="stepper">
    <button aria-label="decrement" @click="count--">-</button>
    {{ count }}
    <button aria-label="increment" @click="count++">+</button>
  </div>
</template>

<script>
  export default {
    props: {
      initial: {
        type: Number,
        default: 0,
        required: false,
      },
    },
    data: () => ({
      count: 0,
    }),
    created() {
      this.count = this.initial
    },
  }
</script>

What Else Should You Test in This Component?

We should also test that the initial prop is used to set the counter, which is reactive and should change when the increment and decrement buttons are clicked.

If we didn't interact with the component, our test wouldn't catch various logic issues -- such as using {{ initial }} in the template, or hard-coding the template to be 100.

Now, let's render and interact with the Stepper component as a user would!

  1. First, ensure that the initial value of the Stepper is correct
const stepperSelector = '[data-testid=stepper]'

it('supports an initial prop', () => {
  cy.mount(Stepper, { props: { initial: 100 } })
  cy.get(stepperSelector).should('contain.text', 100)
})
  1. Next, ensure that you can increment and decrement the Stepper by clicking on the correct buttons.
const stepperSelector = '[data-testid=stepper]'
const incrementSelector = '[aria-label=increment]'
const decrementSelector = '[aria-label=decrement]'

it('can be incremented', () => {
  cy.mount(Stepper)
  cy.get(incrementSelector).click()
  cy.get(stepperSelector).should('contain.text', 1)
})

it('can be decremented', () => {
  cy.mount(Stepper)
  cy.get(decrementSelector).click()
  cy.get(stepperSelector).should('contain.text', -1)
})
  1. Finally, run through the behavior of the Stepper as a user would. There is duplication of coverage here -- but that's okay because it exercises the component in a more real-world usage. This test is more likely to fail if there are any issues in the component, not just with specific buttons or text rendered.
const stepperSelector = '[data-testid=stepper]'
const incrementSelector = '[aria-label=increment]'
const decrementSelector = '[aria-label=decrement]'

it('has an initial counter that can be incremented and decremented', () => {
  cy.mount(Stepper, { props: { initial: 100 } })
  cy.get(stepperSelector).should('contain.text', 100)
  cy.get(incrementSelector).click()
  cy.get(stepperSelector).should('contain.text', 101)
  cy.get(decrementSelector).click().click()
  cy.get(stepperSelector).should('contain.text', 99)
})

Learn More

The Introduction to Cypress guide goes deeper into how to write tests with Cypress.

What's Next?

We're going to emit a custom event from our Stepper component.