Skip to content

Testing properties validation #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
alexjoverm opened this issue Aug 26, 2017 · 13 comments
Closed

Testing properties validation #34

alexjoverm opened this issue Aug 26, 2017 · 13 comments

Comments

@alexjoverm
Copy link

alexjoverm commented Aug 26, 2017

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?

@wparad
Copy link

wparad commented Aug 26, 2017

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.

@LinusBorg
Copy link
Member

LinusBorg commented Aug 26, 2017

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.

@alexjoverm
Copy link
Author

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()
      })

@alexjoverm
Copy link
Author

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?

@eddyerburgh
Copy link
Member

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.

@alexjoverm
Copy link
Author

@eddyerburgh that's true, although I find easier an cleaner to do it as #34 (comment), 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?

@wparad
Copy link

wparad commented Aug 26, 2017

@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 👍

@eddyerburgh
Copy link
Member

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?

@alexjoverm
Copy link
Author

Done ;)

@carlos-contreras
Copy link

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)

@GreggOD
Copy link

GreggOD commented Feb 27, 2019

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.

@HunorMarton
Copy link

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()
  })

@Ge6ben
Copy link

Ge6ben commented Nov 1, 2022

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?

Hi thanks but why it need @ts-ignore to work with typescript ?!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants