Vue-test-utils: Testing properties validation

Created on 26 Aug 2017  ·  12Comments  ·  Source: vuejs/vue-test-utils

How's it possible to test when a property doesn't match a validation rule? So far, I've managed to do it as so, but it feels a bit hacky (jest in the example):

const createCmp = propsData => mount(Message, { propsData })
...
    it('message is of type string', () => {
      let spy = spyOn(console, 'error')
      cmp = createCmp({ message: 1 })
      expect(spy.calls.mostRecent().args[0]).toEqual(expect.stringContaining('Invalid prop'))
    })

Does vue-test-utils provide a better way for this?

Most helpful comment

I've found out an easier way:

it('is required, is a String and validates correctly', () => {
  cmp = createCmp()
  const message = cmp.vm.$options.props.message

  expect(message.required).toBeTruthy()
  expect(message.type).toBe(String)
  expect(message.validator && message.validator('a')).toBeFalsy()
  expect(message.validator && message.validator('aa')).toBeTruthy()
})

At least here is not necessary to use spies, and it works, independently on how you define the props, either in the shorthand way message: String or as an object, since Vue expands them.

Anyway, it would be cleaner if vue-test-utils have a way to access some stuff on the Wrapper:

  • getProps()
  • getAttributes()
  • getOptions()
  • ... others?

The first 2 (in singular) are tracked in #27, but not getOptions() which would be an abstraction to not enter the full internal path .vm.$options.props.message.

What do you think @LinusBorg @wparad?

All 12 comments

That would mean unit testing that Vue is working, which should be out of scope of any test. Instead you are probably interested not in the fact that validation rule works correctly, but that the validation is configured correctly. That should be easier to ensure.

Agree with wparad. you should pass a couple of valid and invalid values to components and verify that the value either passes or doesn't, by checking if the prop's value on the component is undefined.

Well I'd agree if that would work in that way... but how's currently working you cannot test that, since even if the value is invalid, it still passes. Let me explain. Given a Message component with a message property as:

props: {
     message: {
        type: String,
        required: true
      }
}

I'd expect the following test to fail, but it doesn't:

     it('message is of type string', () => {
        cmp = createCmp({ message: 42 })
        expect(cmp.hasProp('message',42)).toBe(true)
        expect(cmp.vm.message).toBe(42)
      })

And of course, if you pass no message, it will be undefined, which doesn't test anything.

I'd like to test that a property of a component indeed is required and validates properly. Right now the cleaner it got is like this:

      let spy = jest.spyOn(console, 'error')
      afterEach(() => spy.mockReset())


      it('message is of type string', () => {
        cmp = createCmp({ message: 1 })
        expect(spy).toBeCalledWith(expect.stringContaining('[Vue warn]: Invalid prop'))
      })

      it('message is required', () => {
        cmp = createCmp()
        expect(spy).toBeCalledWith(expect.stringContaining('[Vue warn]: Missing required prop'))
      })


      it('message has at least length 2', () => {
        cmp = createCmp({ message: 'a' })
        expect(spy).toBeCalledWith(expect.stringContaining('[Vue warn]: Invalid prop'))
      })

      it('message is OK when called with expected props', () => {
        cmp = createCmp({ message: 'hey' })
        expect(spy).not.toBeCalled()
      })

I've found out an easier way:

it('is required, is a String and validates correctly', () => {
  cmp = createCmp()
  const message = cmp.vm.$options.props.message

  expect(message.required).toBeTruthy()
  expect(message.type).toBe(String)
  expect(message.validator && message.validator('a')).toBeFalsy()
  expect(message.validator && message.validator('aa')).toBeTruthy()
})

At least here is not necessary to use spies, and it works, independently on how you define the props, either in the shorthand way message: String or as an object, since Vue expands them.

Anyway, it would be cleaner if vue-test-utils have a way to access some stuff on the Wrapper:

  • getProps()
  • getAttributes()
  • getOptions()
  • ... others?

The first 2 (in singular) are tracked in #27, but not getOptions() which would be an abstraction to not enter the full internal path .vm.$options.props.message.

What do you think @LinusBorg @wparad?

The test you posted in your original post is fine. It tests what you want it to test.

I don't think we should add a method to help test props validation.

@eddyerburgh that's true, although I find easier an cleaner to do it as https://github.com/vuejs/vue-test-utils/issues/34#issuecomment-325139304, since there is no need to spy on globals, or assert in side effects like the console. Do you agree on that? An example could be added to docs as per #18 maybe :)

A side question I was thinking is, if it would be useful to have a getOptions function, just to avoid the vm.$options.props.message call. You know, probably in a major Vue version those internal props change, so it could be an abstraction over. What do you think?

@alexjoverm, I do like. As mentioned above, I like that you are testing the configuration of the validator, so you get a thumbs up from me :+1:

I think there is a case for adding a getOptions method, and would be interested in hearing other opinions. Could you create an issue for it @alexjoverm?

Done ;)

This worked for us:

/*
* Ref: https://jestjs.io/docs/en/expect.html#custom-matchers-api
* this file is required on app/javascript/test/unit/jest.conf.js
*/

const customMatchers = {
  toTriggerError(received) {
    const spy = spyOn(console, "error")

    received()

    const pass = !!spy.calls.mostRecent() && spy.calls.mostRecent().args[0].includes("[Vue warn]:")

    return {
      message() {
        return `expected ${received} to trigger error`
      },
      pass,
    }
  },
}

expect.extend(customMatchers)

Any ideas how to do this on a functional component seeing as jest doesn't allow you to access props of a functional?

Using Vue CLI 3.

If you define your components simply as an object:

export default {
  name: 'Component',
  props: {
    message: {
      type: String,
      required: true,
      validator: function(value) {
        return value == 'Hello'
      }
    } 
  }
}

Then you save yourself a lot of trouble:

  test('props validator', () => {
    const message = Component.props.message

    expect(message.validator).toBeInstanceOf(Function)
    expect(message.validator('Hello')).toBeTruthy()
    expect(message.validator('Hi')).toBeFalsy()
  })
Was this page helpful?
0 / 5 - 0 ratings