Categories
Vue 3 Testing

Testing Vue 3 Apps — Custom Inputs and Slots

With apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue 3 apps by writing a simple app and testing it.

Test Multiple Modifiers

We can test multiple event modifiers applied to events.

For example, we can write:

import { mount } from '@vue/test-utils'

const FormComponent = {
  template: `
    <div>
      <input @keydown.meta.c.exact.prevent="captureCopy" v-model="input" />
    </div>
  `,
  data() {
    return {
      input: ''
    }
  },
  methods: {
    captureCopy() {
      this.$emit('submit', this.input)
    }
  }
}

test('handles complex events', async () => {
  const wrapper = mount(FormComponent)
  await wrapper.find('input').trigger('keydown.meta.c.exact.prevent')
  expect(wrapper.emitted()).toHaveProperty('submit')
})

We have the FormComponent with an input that has the keydown event listener attached to it.

It has various modifiers applied to it.

Then in our test, we call trigger the keydown event with all the modifiers:

await wrapper.find('input').trigger('keydown.meta.c.exact.prevent')

Then we check if the submit event is emitted in the last line.

Vue Test Utils reads the event and applies the appropriate properties to the event object.

Interacting with Vue Component Inputs

We can interact with component inputs.

For example, we can write:

import { mount } from '@vue/test-utils'

const CustomInput = {
  template: `
    <div>
      <label>
        {{ label }}
        <input
          type="text"
          :value="modelValue"
          [@input](https://medium.com/r/?url=http%3A%2F%2Ftwitter.com%2Finput "Twitter profile for @input")="$emit('update:modelValue', $event.target.value)"
        >
      </label>
    </div>
  `,
  name: 'CustomInput',
  props: ['modelValue', 'label']
}

const Form = {
  template: `
    <div>
      <custom-input v-model="input" label="Text Input" class="text-input"/>
    </div>
  `,
  components: {
    CustomInput
  }
}

test('fills in the form', async () => {
  const wrapper = mount(Form)
  await wrapper.find('.text-input input').setValue('text')
  expect(wrapper.find('.text-input input').element.value).toBe('text')
})

We mount the Form component with the mount method.

Then we get the input from the custom-input and call setValue to set its value.

Then we check the value of the input in the last line.

Slots

We can populate slots of a given component and test it.

For example, we can write:

import { mount } from '@vue/test-utils'

const Layout = {
  template: `
    <div>
      <h1>Welcome!</h1>
      <main>
        <slot />
      </main>
      <footer>
        Thanks for visiting.
      </footer>
    </div>
  `
}

test('layout default slot', () => {
  const wrapper = mount(Layout, {
    slots: {
      default: 'Main Content'
    }
  })
  expect(wrapper.html()).toContain('Main Content')
})

We have the slots property in the object we pass into mount .

The default property populates the default slot.

Then we check that the rendered HTML has the 'Main Content' text.

Testing Named Slots

We can populate named slots and test its rendered content.

For example, we can write:

import { mount } from '@vue/test-utils'

const Layout = {
  template: `
    <div>
      <header>
        <slot name="header" />
      </header>
      <main>
        <slot name="main" />
      </main>
      <footer>
        <slot name="footer" />
      </footer>
    </div>
  `
}

test('layout full page layout', () => {
  const wrapper = mount(Layout, {
    slots: {
      header: '<div>Header</div>',
      main: '<div>Main Content</div>',
      footer: '<div>Footer</div>'
    }
  })
  expect(wrapper.html()).toContain('<div>Header</div>')
  expect(wrapper.html()).toContain('<div>Main Content</div>')
  expect(wrapper.html()).toContain('<div>Footer</div>')
})

We have the Layout component with multiple slots.

Then in the test, we mount the components with all the slots filled.

The keys have the slot names, the values are the HTML we want to put inside it.

Then we can check the HTML that’s rendered in the last 3 lines.

Conclusion

We can test Vue 3 custom input components and slots with Vue Test Utils.

Categories
Vue 3 Testing

Testing Vue 3 Apps — Form Triggered Events and Set Values of Form Elements

With apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue 3 apps by writing a simple app and testing it.

Testing Form Triggered Events

We can test the events that are triggered when we submit the form.

For example, we can write:

import { mount } from '@vue/test-utils'

const EmailInput = {
  template: `
    <div>
      <input type="email" v-model="email">
      <button @click="submit">Submit</button>
    </div>
  `,
  data() {
    return {
      email: ''
    }
  },
  methods: {
    submit() {
      this.$emit('submit', this.email)
    }
  }
}

test('sets the value', async () => {
  const wrapper = mount(EmailInput)
  const input = wrapper.find('input')
  await input.setValue('abc@mail.com')
  expect(input.element.value).toBe('abc@mail.com')
})

test('submit event is triggered', async () => {
  const wrapper = mount(EmailInput)
  await wrapper.find('button').trigger('click')
  expect(wrapper.emitted()).toHaveProperty('submit')
})

In the 2nd test, we get the button and trigger the click event.

Then we check that the submit event is emitted with the wrapper.emitted method.

We can check what the event triggered with the argument that it’s emitted with.

For example, we can write:

import { mount } from '@vue/test-utils'

const EmailInput = {
  template: `
    <div>
      <input type="email" v-model="email">
      <button @click="submit">Submit</button>
    </div>
  `,
  data() {
    return {
      email: ''
    }
  },
  methods: {
    submit() {
      this.$emit('submit', this.email)
    }
  }
}

test('emits the input to its parent', async () => {
  const wrapper = mount(EmailInput)
  await wrapper.find('input').setValue('abc@mail.com')
  await wrapper.find('button').trigger('click')
  expect(wrapper.emitted('submit')[0][0]).toBe('abc@mail.com')
})

We mount the EmailInput .

Then we call setValue on the input to set its value.

Then we trigger the click event on the button.

And we check that the submit event is emitted with the input value.

Working with Various Form Elements

We can manipulate other form elements in our tests.

For instance, we can write:

import { mount } from '@vue/test-utils'

const FormComponent = {
  template: `
    <div>
      <form @submit.prevent="submit">
        <input type="email" v-model="form.email">
        <textarea v-model="form.description"/>

        <select v-model="form.city">
          <option value="new-york">New York</option>
          <option value="toronto">Toronto</option>
        </select>

        <input type="checkbox" v-model="form.subscribe"/>
        <input type="radio" value="weekly" v-model="form.interval"/>
        <input type="radio" value="monthly" v-model="form.interval"/>

         <button type="submit">Submit</button>
      </form>
    </div>
  `,
  data() {
    return {
      form: {
        email: '',
        description: '',
        city: '',
        subscribe: false,
        interval: ''
      }
    }
  },
  methods: {
    async submit() {
      this.$emit('submit', this.form)
    }
  }
}

test('submits a form', async () => {
  const wrapper = mount(FormComponent)
  await wrapper.find('input[type=email]').setValue('name@mail.com')
  await wrapper.find('textarea').setValue('Lorem ipsum')
  await wrapper.find('select').setValue('toronto')
  await wrapper.find('input[type=checkbox]').setValue()
  await wrapper.find('input[type=radio][value=monthly]').setValue()
})

We can use the setValue method with textarea , selecrt , checkboxes and radio buttons.

Also, we can trigger complex events.

For instance, we can write:

import { mount } from '@vue/test-utils'

const FormComponent = {
  template: `
    <div>
      <form @submit.prevent="submit">
        <input type="email" v-model="form.email">
        <textarea v-model="form.description"/>

        <select v-model="form.city">
          <option value="new-york">New York</option>
          <option value="toronto">Toronto</option>
        </select>

        <input type="checkbox" v-model="form.subscribe"/>
        <input type="radio" value="weekly" v-model="form.interval"/>
        <input type="radio" value="monthly" v-model="form.interval"/>

        <button type="submit">Submit</button>
      </form>
    </div>
  `,
  data() {
    return {
      form: {
        email: '',
        description: '',
        city: '',
        subscribe: false,
        interval: ''
      }
    }
  },
  methods: {
    async submit() {
      this.$emit('submit', this.form)
    }
  }
}

test('submits a form', async () => {
  const wrapper = mount(FormComponent)
  const email = 'name@mail.com'
  const description = 'Lorem ipsum'
  const city = 'toronto'
  await wrapper.find('input[type=email]').setValue(email)
  await wrapper.find('textarea').setValue(description)
  await wrapper.find('select').setValue(city)
  await wrapper.find('input[type=checkbox]').setValue()
  await wrapper.find('input[type=radio][value=monthly]').setValue()
  await wrapper.find('form').trigger('submit.prevent')
  expect(wrapper.emitted('submit')[0][0]).toMatchObject({
    email,
    description,
    city,
    subscribe: true,
    interval: 'monthly',
  })
})

We have the same component.

In the test, we mount the component and call setValue to set the value of each field.

Then we check if the object we emitted with the submit event matches the expected object with the toMatchObject method.

We test the this.form object in the submit method matches what we have in the last line.

Conclusion

We can test form triggered events and set the values of various form elements in our Vue 3 component tests with Vue Test Utils.

Categories
Vue 3 Testing

Testing Vue 3 Apps — Reactive Properties and Form Handling

With apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue 3 apps by writing a simple app and testing it.

Passing Data to Components

We can pass data to components.

For example, we can write:

import { mount } from '@vue/test-utils'

const Name = {
  template: `
    <div>
      <input v-model="name">
      <div v-if="error">{{ error }}</div>
    </div>
  `,
  props: {
    minLength: {
      type: Number
    }
  },
  computed: {
    error() {
      if (this.name.length < this.minLength) {
        return `Name must be at least ${this.minLength} characters.`
      }
      return
    }
  }
}

test('renders an error if length is too short', () => {
  const wrapper = mount(Name, {
    props: {
      minLength: 10
    },
    data() {
      return {
        name: 'short'
      }
    }
  })
  expect(wrapper.html()).toContain('Name must be at least 10 characters')
})

We have the Name component with an input field and an error display.

The error computed property checks if the name is too short and show an error message if it is.

In the test, we pass in the minLength prop to the component.

And the data method has the name reactive property set.

Then we show the error message since the name value’s length is shorter than 10.

Using setProps

We can also use the setProps method to set the props.

For example, we can write:

import { mount } from '@vue/test-utils'

const Show = {
  template: `
    <div>
      <div v-if="show">{{ greeting }}</div>
    </div>
  `,
  props: {
    show: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      greeting: 'Hello'
    }
  }
}

test('renders a greeting when show is true', async () => {
  const wrapper = mount(Show)
  expect(wrapper.html()).toContain('Hello')
  await wrapper.setProps({ show: false })
  expect(wrapper.html()).not.toContain('Hello')
})

We test the Show component and check if the 'Hello' is rendered in the component.

Then we call setProps to set the show prop to false .

Then we check that 'Hello' isn’t rendered.

Test Form Handling

We can test form handling by interacting with form elements.

For example, we can write:

import { mount } from '@vue/test-utils'

const EmailInput = {
  template: `
    <div>
      <input type="email" v-model="email">
      <button @click="submit">Submit</button>
    </div>
  `,
  data() {
    return {
      email: ''
    }
  },
  methods: {
    submit() {
      this.$emit('submit', this.email)
    }
  }
}

test('sets the value', async () => {
  const wrapper = mount(EmailInput)
  const input = wrapper.find('input')
  await input.setValue('abc@mail.com')
  expect(input.element.value).toBe('abc@mail.com')
})

We have an EmailInput component with an input component.

Then we add a test to mount the EmailInput component.

Then we get the input with find .

And then we call input.setValue to set its value.

Then we get the value from the input with input.element.value .

Conclusion

We can pass in the data for the props and test the rendered Vue 3 components with Vue Test Utils.

Also, we can test form inputs with this.

Categories
Vue 3 Testing

Testing Vue 3 Apps — Testing Event Handling

With apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue 3 apps by writing a simple app and testing it.

Event Handling

We can test event handling in our Vue 3 components easily with Vue Test Utils.

For example, we can write:

import { mount } from '@vue/test-utils'

const Counter = {
  template: '<button @click="handleClick">Increment</button>',
  data() {
    return {
      count: 0
    }
  },
  methods: {
    handleClick() {
      this.count += 1
      this.$emit('increment', this.count)
    }
  }
}

test('emits increment event when button is clicked', () => {
  const wrapper = mount(Counter)
  wrapper.find('button').trigger('click')
  expect(wrapper.emitted()).toHaveProperty('increment')
})

We have the Counter component that has a button that runs the handleclick method when we click it.

The handleClick method emits the increment event.

In the test, we mount the Counter component.

Then we get the button with find and call trigger with the click method.

And then we check if the increment event is emitted with the wrapper.emitted method.

The returned object has the event names as the key.

We can do other checks in our tests with regard to events.

For instance, we can write:

import { mount } from '@vue/test-utils'

const Counter = {
  template: '<button @click="handleClick">Increment</button>',
  data() {
    return {
      count: 0
    }
  },
  methods: {
    handleClick() {
      this.count += 1
      this.$emit('increment', this.count)
    }
  }
}

test('emits increment event when button is clicked', () => {
  const wrapper = mount(Counter)
  wrapper.find('button').trigger('click')
  expect(wrapper.emitted()).toHaveProperty('increment')
  const incrementEvent = wrapper.emitted('increment')
  expect(incrementEvent).toHaveLength(1)
  expect(incrementEvent[0]).toEqual([1])

})

We get the increment event object with:

const incrementEvent = wrapper.emitted('increment')

Then we check how many time it’s emitted with:

expect(incrementEvent).toHaveLength(1)

And we check the value that it’s emitted with, which the 2nd argument of this.$emit with:

expect(incrementEvent[0]).toEqual([1])

Asserting Complex Events

We can test more complex event objects.

For example, we can write:

import { mount } from '@vue/test-utils'

const Counter = {
  template: '<button @click="handleClick">Increment</button>',
  data() {
    return {
      count: 0
    }
  },
  methods: {
    handleClick() {
      this.count += 1
      this.$emit('increment', {
        count: this.count,
        isEven: this.count % 2 === 0
      })
    }
  }
}

test('emits increment event when button is clicked', () => {
  const wrapper = mount(Counter)
  wrapper.find('button').trigger('click')
  wrapper.find('button').trigger('click')
  expect(wrapper.emitted('increment')).toHaveLength(2)
  const [event1, event2] = wrapper.emitted('increment')
  expect(event1).toEqual([
    {
      count: 1,
      isEven: false
    }
  ])
  expect(event2).toEqual([
    {
      count: 2,
      isEven: true
    }
  ])
})

We trigger the click event on the button twice.

Then we check the number of times the increment event is emitted with:

expect(wrapper.emitted('increment')).toHaveLength(2)

Then we get the event objects that are emitted with:

const [event1, event2] = wrapper.emitted('increment')

Then we check the event object that we emitted with the event with:

expect(event1).toEqual([{
  count: 1,
  isEven: false
}])
expect(event2).toEqual([{
  count: 2,
  isEven: true
}])

Conclusion

We can test event emissions with Vue 3 and Vue Test Utils.

Categories
Vue 3 Testing

Getting Started with Testing Vue 3 Apps

With apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue 3 apps by writing a simple app and testing it.

Getting Started with Testing

We can get started with testing by creating the project with Vue CLI.

We add the ‘unit testing’ option when we create then project.

Then the files and settings for creating and running unit tests will be added.

We should see the tests folder with the test files.

Now we can create our own test by writing:

import { mount } from '@vue/test-utils'

const MessageComponent = {
  template: '<p>{{ msg }}</p>',
  props: ['msg'],
}

test('displays message', () => {
  const wrapper = mount(MessageComponent, {
    props: {
      msg: 'Hello world'
    }
  })
  expect(wrapper.text()).toContain('Hello world')
})

Then we can run npm run test:unit to run the test.

We should see a checkmark since ‘Hello World’ is rendered as the prop.

We call mount to mount the MessageComponent .

And the props has the props.

Then we call wrapper.text to get the text of the rendered component.

test takes a string that has the test description and the callback has the test code.

Conditional Rendering

We can test conditional rendering as we do in the first example.

For example, we can write:

import { mount } from '@vue/test-utils'

const Nav = {
  template: `
    <nav>
      <a id="profile" href="/profile">Profile</a>
      <a v-if="admin" id="admin" href="/admin">Admin</a>
    </nav>
  `,
  data() {
    return {
      admin: false
    }
  }
}

test('displays profile', () => {
  const wrapper = mount(Nav)
  const profileLink = wrapper.get('#profile')
  expect(profileLink.text()).toEqual('Profile')
})

We have the Nav component and get the element with the ID profile with wrapper.get .

Then we get the text with the text method.

We can check whether an element with the given selector exists with:

import { mount } from '@vue/test-utils'

const Nav = {
  template: `
    <nav>
      <a id="profile" href="/profile">Profile</a>
      <a v-if="admin" id="admin" href="/admin">Admin</a>
    </nav>
  `,
  data() {
    return {
      admin: false
    }
  }
}

test('displays profile', () => {
  const wrapper = mount(Nav)
  const profileLink = wrapper.get('#profile')
  expect(profileLink.text()).toEqual('Profile')
})

test('does not render an admin link', () => {
  const wrapper = mount(Nav)
  expect(wrapper.find('#admin').exists()).toBe(false)
})

wrapper.find(‘#admin’).exists() tries to find the element with the given selector.

Also, we can pass in our own data to change the value of the admin reactive property and test it:

import { mount } from '@vue/test-utils'

const Nav = {
  template: `
    <nav>
      <a id="profile" href="/profile">Profile</a>
      <a v-if="admin" id="admin" href="/admin">Admin</a>
    </nav>
  `,
  data() {
    return {
      admin: true
    }
  }
}

test('renders an admin link', () => {
  const wrapper = mount(Nav, {
    data() {
      return {
        admin: true
      }
    }
  })
  expect(wrapper.get('#admin').text()).toEqual('Admin')
})

We pass in our own data function in the test so we set admin to true .

And now we can check that the Admin text is rendered along with the link.

Conclusion

We can test rendered Vue 3 components with Vue Test Utils.