Skip to content

Latest commit

 

History

History
684 lines (530 loc) · 20.8 KB

README.md

File metadata and controls

684 lines (530 loc) · 20.8 KB

@cypress/vue

NPM

semantic-release

Browser-based Component Testing for Vue.js with the Open-Source Cypress.io Test Runner ✌️🌲

✨ New We're growing the Cypress Community Discord. We have dedicated sections on Component Testing. 👉 Join now and let's chat!

Jump to: Comparison, Blog posts, Examples: basic, advanced, full, external, Code coverage, Development

What is @cypress/vue?

This package allows you to use the Cypress test runner to mount and test your components within Cypress. It is built on top of the Vue Test Utils package.

Example component test

How is this different from Vue Test Utils?

It uses Vue Test Utils under the hood. This is more of a replacement for node-based testing than it is replacing Vue Test Utils and its API. Instead of running your tests in node (using Jest or Mocha), the Cypress Component Testing Library runs each component in the real browser with full power of the Cypress Framework: live GUI, full API, screen recording, CI support, cross-platform. One benefit to using Cypress instead of a node-based runner is that limitations of Vue Test Utils in Node (e.g. manually awaiting Vue's internal event loop) are hidden from the user due to Cypress's retry-ability logic.

  • If you like using @testing-library/vue, you can use @testing-library/cypress for the same findBy, queryBy commands, see one of the examples in the list below

Installation

  • Requires Cypress v7.0.0 or later
  • Requires Node version 12 or above
  • Supports webpack-based projects, vite in alpha, if you would like us to support another, please create an issue or, if an issue already exists subscribe to it.

Now you are ready to install.

Manual Installation

Using @cypress/webpack-dev-server and vue-loader.

// cypress/plugins/index.js
const webpack = require('@cypress/webpack-dev-server')
const webpackOptions = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
    ],
  },
}

const options = {
  // send in the options from your webpack.config.js, so it works the same
  // as your app's code
  webpackOptions,
  watchOptions: {},
}

module.exports = (on) => {
  on('dev-server:start', webpack(options))
}

Install dev dependencies

npm i -D @cypress/webpack-dev-server \
  vue-loader vue-template-compiler css-loader

And write a test

import Hello from '../../components/Hello.vue'
import { mountCallback } from '@cypress/vue'

describe('Hello.vue', () => {
  beforeEach(mountCallback(Hello))

  it('shows hello', () => {
    cy.contains('Hello World!')
  })
})

Usage and Examples

// components/HelloWorld.spec.js
import { mount } from '@cypress/vue'
import { HelloWorld } from './HelloWorld.vue'
describe('HelloWorld component', () => {
  it('works', () => {
    mount(HelloWorld)
    // now use standard Cypress commands
    cy.contains('Hello World!').should('be.visible')
  })
})

Options

You can pass additional styles, css files and external stylesheets to load, see docs/styles.md for full list.

import Todo from './Todo.vue'
const todo = {
  id: '123',
  title: 'Write more tests',
}

mount(Todo, {
  propsData: { todo },
  stylesheets: [
    'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.css',
  ],
})

See examples below for details.

Global Vue Options

You can pass extensions (global components, mixins, modules to use) when mounting Vue component. Use { extensions: { ... }} object inside the options.

  • components - object of 'id' and components to register globally, see Components example
  • use (alias plugins) - list of plugins, see Plugins
  • mixin (alias mixins) - list of global mixins, see Mixins example
  • filters - hash of global filters, see Filters example

intro example

Take a look at the first Vue v2 example: Declarative Rendering. The code is pretty simple

<div id="app">
  {{ message }}
</div>
var app = new Vue({
  el: '#app',
  data() {
    return { message: 'Hello Vue!' }
  },
})

It shows the message when running in the browser

Hello Vue!

Let's test it in Cypress.io (for the current version see cypress/integration/spec.js).

import { mountCallback } from '@cypress/vue'

describe('Declarative rendering', () => {
  // Vue code from https://vuejs.org/v2/guide/#Declarative-Rendering
  const template = `
    <div id="app">
      {{ message }}
    </div>
  `

  const data = {
    message: 'Hello Vue!',
  }

  // that's all you need to do
  beforeEach(mountCallback({ template, data }))

  it('shows hello', () => {
    cy.contains('Hello Vue!')
  })

  it('changes message if data changes', () => {
    // mounted Vue instance is available under Cypress.vue
    Cypress.vue.message = 'Vue rocks!'
    cy.contains('Vue rocks!')
  })
})

Fire up Cypress test runner and have real browser (Electron, Chrome) load Vue and mount your test code and be able to interact with the instance through the reference Cypress.vue.$data and via GUI. The full power of the Cypress API is available.

Hello world tested

list example

There is a list example next in the Vue docs.

<div id="app-4">
  <ol>
    <li v-for="todo in todos">
      {{ todo.text }}
    </li>
  </ol>
</div>
var app4 = new Vue({
  el: '#app-4',
  data: {
    todos: [
      { text: 'Learn JavaScript' },
      { text: 'Learn Vue' },
      { text: 'Build something awesome' },
    ],
  },
})

Let's test it. Simple.

import { mountCallback } from '@cypress/vue'

describe('Declarative rendering', () => {
  // List example from https://vuejs.org/v2/guide/#Declarative-Rendering
  const template = `
    <ol>
      <li v-for="todo in todos">
        {{ todo.text }}
      </li>
    </ol>
  `

  function data() {
    return {
      todos: [
        { text: 'Learn JavaScript' },
        { text: 'Learn Vue' },
        { text: 'Build something awesome' },
      ],
    }
  }

  beforeEach(mountCallback({ template, data }))

  it('shows 3 items', () => {
    cy.get('li').should('have.length', 3)
  })

  it('can add an item', () => {
    Cypress.vue.todos.push({ text: 'Test using Cypress' })
    cy.get('li').should('have.length', 4)
  })
})

List tested

Handling User Input

The next section in the Vue docs starts with reverse message example.

<div id="app-5">
  <p>{{ message }}</p>
  <button @click="reverseMessage">Reverse Message</button>
</div>
var app5 = new Vue({
  el: '#app-5',
  data: {
    message: 'Hello Vue.js!',
  },
  methods: {
    reverseMessage: function () {
      this.message = this.message.split('').reverse().join('')
    },
  },
})

We can write the test the same way

import { mountCallback } from '@cypress/vue'

describe('Handling User Input', () => {
  // Example from https://vuejs.org/v2/guide/#Handling-User-Input
  const template = `
    <div>
      <p>{{ message }}</p>
      <button @click="reverseMessage">Reverse Message</button>
    </div>
  `

  function data() {
    return { message: 'Hello Vue.js!' }
  }

  const methods = {
    reverseMessage: function () {
      this.message = this.message.split('').reverse().join('')
    },
  }

  beforeEach(mountCallback({ template, data, methods }))

  it('reverses text', () => {
    cy.contains('Hello Vue')
    cy.get('button').click()
    cy.contains('!sj.euV olleH')
  })
})

Take a look at the video of the test. When you hover over the CLICK step the test runner is showing before and after DOM snapshots. Not only that, the application is fully functioning, you can interact with the application because it is really running!

Reverse input

Let us test a complex example. Let us test a single file Vue component. Here is the Hello.vue file

<template>
  <p>{{ greeting }} World!</p>
</template>

<script>
export default {
  data() {
    return {
      greeting: 'Hello',
    }
  },
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

note to learn how to load Vue component files in Cypress, see Bundling section.

Do you want to interact with the component? Go ahead! Do you want to have multiple components? No problem!

import Hello from '../../components/Hello.vue'
import { mountCallback } from '@cypress/vue'
describe('Several components', () => {
  const template = `
    <div>
      <hello></hello>
      <hello></hello>
      <hello></hello>
    </div>
  `
  const components = {
    hello: Hello,
  }
  beforeEach(mountCallback({ template, components }))

  it('greets the world 3 times', () => {
    cy.get('p').should('have.length', 3)
  })
})

Spying example

Button counter component is used in several Vue doc examples

<template>
  <button @click="incrementCounter">{{ counter }}</button>
</template>

<script>
export default {
  data() {
    return {
      counter: 0,
    }
  },

  methods: {
    incrementCounter: function () {
      this.counter += 1
      this.$emit('increment')
    },
  },
}
</script>

<style scoped>
button {
  margin: 5px 10px;
  padding: 5px 10px;
  border-radius: 3px;
}
</style>

Let us test it - how do we ensure the event is emitted when the button is clicked? Simple - let us spy on the event, spying and stubbing is built into Cypress

import ButtonCounter from '../../components/ButtonCounter.vue'
import { mountCallback } from '@cypress/vue'

describe('ButtonCounter', () => {
  beforeEach(mountCallback(ButtonCounter))

  it('starts with zero', () => {
    cy.contains('button', '0')
  })

  it('increments the counter on click', () => {
    cy.get('button').click().click().click().contains('3')
  })

  it('emits "increment" event on click', () => {
    const spy = cy.spy()
    Cypress.vue.$on('increment', spy)
    cy.get('button')
      .click()
      .click()
      .then(() => {
        expect(spy).to.be.calledTwice
      })
  })
})

The component is really updating the counter in response to the click and is emitting an event.

Spying test

The mount function automatically wraps XMLHttpRequest giving you an ability to intercept XHR requests your component might do. For full documentation see Network Requests. In this repo see components/AjaxList.vue and the corresponding tests cypress/integration/ajax-list-spec.js.

// component use axios to get list of users
created() {
  axios.get(`https://jsonplaceholder.cypress.io/users?_limit=3`)
  .then(response => {
    // JSON responses are automatically parsed.
    this.users = response.data
  })
}
// test can observe, return mock data, delay and a lot more
beforeEach(mountCallback(AjaxList))
it('can inspect real data in XHR', () => {
  cy.server()
  cy.route('/users?_limit=3').as('users')
  cy.wait('@users').its('response.body').should('have.length', 3)
})
it('can display mock XHR response', () => {
  cy.server()
  const users = [{id: 1, name: 'foo'}]
  cy.route('GET', '/users?_limit=3', users).as('users')
  cy.get('li').should('have.length', 1)
    .first().contains('foo')
})

Calls to window.alert are automatically recorded, but do not show up. Instead you can spy on them, see AlertMessage.vue and its test cypress/integration/alert-spec.js

Comparison

Feature Vue Test Utils or @testing-library/vue Cypress + @cypress/vue
Test runs in real browser
Uses full mount
Test speed 🏎 as fast as the app works in the browser
Test can use additional plugins maybe use any Cypress plugin
Test can interact with component synthetic limited API use any Cypress command
Test can be debugged via terminal and Node debugger use browser DevTools
Built-in time traveling debugger Cypress time traveling debugger
Re-run tests on file or test change
Test output on CI terminal terminal, screenshots, videos
Tests can be run in parallel ✅ via parallelization
Test against interface if using @testing-library/vue ✅ and can use @testing-library/cypress
Spying and mocking Jest mocks Sinon library
Code coverage

Examples

// components/HelloWorld.spec.js
import { mount } from '@cypress/vue'
import { HelloWorld } from './HelloWorld.vue'
describe('HelloWorld component', () => {
  it('works', () => {
    mount(HelloWorld)
    // now use standard Cypress commands
    cy.contains('Hello World!').should('be.visible')
  })
})

Basic examples

Spec Description
Components Registers global components to use
Filters Registering global filters
Hello Testing examples from Vue2 cookbook
Mixins Registering Vue mixins
Plugins Loading additional plugins
Props Pass props to the component during mount
Slots Passing slots and scopedSlots to the component
Small examples A few small examples testing forms, buttons

Advanced examples

Spec Description
access-component Access the mounted component directly from test
i18n Testing component that uses Vue I18n plugin
mocking-axios Mocking 3rd party CommonJS modules like axios
mocking-fetch Mocking window.fetch to stub responses and test the UI
fetch-polyfill Using experimental fetch polyfill to spy on / stub those Ajax requests using regular Cypress network methods
mocking-components Mocking locally registered child components during tests
mocking-imports Stub ES6 imports from the tests
render-functions Mounting components with a render function

Full examples

We have several subfolders in examples folder.

Folder Name Description
cli An example app scaffolded using Vue CLI and the component testing added using vue add cypress-experimental command.

External examples

Repo Description
vue-component-test-example Scaffolded Vue CLI v3 project with added component tests, read Write Your First Vue Component Test.

Code coverage

This plugin uses babel-plugin-istanbul to automatically instrument .js and .vue files and generates the code coverage report using dependency cypress-io/code-coverage (included). The output reports are saved in the folder "coverage" at the end of the test run.

If you want to disable code coverage instrumentation and reporting, use --env coverage=false or CYPRESS_coverage=false or set in your cypress.json file

{
  "env": {
    "coverage": false
  }
}

Note ⚠️: if the component .vue file does not have a <script> section, it will not have any code coverage information.

What happened to cypress-vue-unit-test?

We were in the middle of moving into the Cypress NPM org, so any references to cypress-vue-unit-test should be switched to @cypress/vue. Once complete, the old repository will be archived.

Development

To see all local tests, install dependencies, build the code and open Cypress using the open-ct command

yarn install
yarn workspace @cypress/vue build

The build is done using rollup. It bundles all files from src to the dist folder. You can then run component tests by opening Cypress

# cypress open-ct
yarn workspace @cypress/vue cy:open

Larger tests that use full application and run on CI (see circle.yml) are located in the folder examples.

Debugging

Run Cypress with environment variable

DEBUG=@cypress/vue

If some deeply nested objects are abbreviated and do not print fully, set the maximum logging depth

DEBUG=@cypress/vue DEBUG_DEPTH=10

Related info

Blog posts

Test adapters for other frameworks

Maintainers

The Cypress.io Component Testing Team

Support: if you find any problems with this module, tweet / open issue on Github

License

license

This project is licensed under the terms of the MIT license.

Badges

Let the world know your project is using Cypress.io to test with this cool badge

Cypress.io