How can I test a custom input Vue component

Jason Desrosiers picture Jason Desrosiers · Mar 11, 2018 · Viewed 13.5k times · Source

In the Vue.js documentation, there is an example of a custom input component. I'm trying to figure out how I can write a unit test for a component like that. Usage of the component would look like this

<currency-input v-model="price"></currency-input>

The full implementation can be found at https://vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events

The documentation says

So for a component to work with v-model, it should (these can be configured in 2.2.0+):

  • accept a value prop
  • emit an input event with the new value

How do I write a unit test that ensures that I've written this component such that it will work with v-model? Ideally, I don't want to specifically test for those two conditions, I want to test the behavior that when the value changes within the component, it also changes in the model.

Answer

acdcjunior picture acdcjunior · Mar 12, 2018

You can do it:

  • Using Vue Test Utils, and
  • Mounting a parent element that uses <currency-input>
  • Fake an input event to the inner text field of <currency-input> with a value that it transforms (13.467 is transformed by <currency-input> to 13.46)
  • Verify if, in the parent, the price property (bound to v-model) has changed.

Example code (using Mocha):

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

describe('CurrencyInput.vue', () => {
  it("changing the element's value, updates the v-model", () => {
    var parent = mount({
      data: { price: null },
      template: '<div> <currency-input v-model="price"></currency-input> </div>',
      components: { 'currency-input': CurrencyInput }
    })

    var currencyInputInnerTextField = parent.find('input');
    currencyInputInnerTextField.element.value = 13.467;
    currencyInputInnerTextField.trigger('input');

    expect(parent.vm.price).toBe(13.46);
  });
});

In-browser runnable demo using Jasmine:

var CurrencyInput = Vue.component('currency-input', {
  template: '\
    <span>\
      $\
      <input\
        ref="input"\
        v-bind:value="value"\
        v-on:input="updateValue($event.target.value)">\
    </span>\
  ',
  props: ['value'],
  methods: {
    // Instead of updating the value directly, this
    // method is used to format and place constraints
    // on the input's value
    updateValue: function(value) {
      var formattedValue = value
        // Remove whitespace on either side
        .trim()
        // Shorten to 2 decimal places
        .slice(0, value.indexOf('.') === -1 ? value.length : value.indexOf('.') + 3)
      // If the value was not already normalized,
      // manually override it to conform
      if (formattedValue !== value) {
        this.$refs.input.value = formattedValue
      }
      // Emit the number value through the input event
      this.$emit('input', Number(formattedValue))
    }
  }
});



// specs code ///////////////////////////////////////////////////////////
var mount = vueTestUtils.mount;
describe('CurrencyInput', () => {
  it("changing the element's value, updates the v-model", () => {
    var parent = mount({
      data() { return { price: null } },
      template: '<div> <currency-input v-model="price"></currency-input> </div>',
      components: { 'currency-input': CurrencyInput }
    });
    
    var currencyInputInnerTextField = parent.find('input');
    currencyInputInnerTextField.element.value = 13.467;
    currencyInputInnerTextField.trigger('input');

    expect(parent.vm.price).toBe(13.46);
  });
});

// load jasmine htmlReporter
(function() {
  var env = jasmine.getEnv()
  env.addReporter(new jasmine.HtmlReporter())
  env.execute()
}())
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css">
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
<script src="https://npmcdn.com/[email protected]/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/browser.js"></script>
<script src="https://rawgit.com/vuejs/vue-test-utils/2b078c68293a41d68a0a98393f497d0b0031f41a/dist/vue-test-utils.iife.js"></script>

Note: The code above works fine (as you can see), but there can be improvements to tests involving v-model soon. Follow this issue for up-to-date info.