Skip to content
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

add section to docs on testing components that include promises #151

Closed
lmiller1990 opened this issue Nov 4, 2017 · 9 comments
Closed

Comments

@lmiller1990
Copy link
Member

lmiller1990 commented Nov 4, 2017

First, I am not sure if this is something vue-test-utils should do or something the test runner should handle. It's just an idea I had to solve a problem I found a lot while using vue-test-utils.

A number of libraries (for example vee validate, which I have been using heavily in my app) rely on promises. In the case of vee validate, errors are rendered async. vue-test-utils does everything sync. Another common test case is asserting some UI changed based on a mock API response, that uses a promise.

Both these cases require the test to wait for a promise to resolve (even if it is one that resolves almost immediately). vue-test-utils is all sync, I have to use the test runner (in this case jest) to await the promise, or the wrapper.vm is not updated.

The below example shows the first case (failing for the reason outlined above) and a workaround.

My original idea was a triggerAsync method:

wrapper.find('input').triggerAsync('click', (done) => {
  // wait for promises to resolve
  // wrapper.update() so computed/data values, and UI is updated
  // assertions
  done()
})

However I am starting to think I am heading down the wrong path.

Example:
Component

<template>
  <div id="app">
    {{ msg }}
    <button @click="handle">Button</button>
</template>

<script>
export default {
  name: 'app',

  data () {
    return {
      msg: '',
    }
  },

  methods: {
    promise () {
      return new Promise(res => {
        res()
      })
    },

    handle () {
      this.promise()
        .then(() => {
          this.msg = 'testing'
        })
    }
  }
}
</script>

Test:

import { shallow } from 'vue-test-utils'
import App from './App'

describe('App', () => {
  // fails because async promise
  it('sets some text', () => {
    const wrapper = shallow(App)

    wrapper.find('button').trigger('click')

    expect(wrapper.vm.msg).toBe('testing')
  })

// returning a promise in jest lets this pass
it('sets some text', () => {
    const wrapper = shallow(App)

    return new Promise(res => {
      wrapper.find('button').trigger('click')
      res()
    })
    .then(() => {
       expect(wrapper.vm.msg).toBe('testing')
    })
  })
})

https://github.com/lmiller1990/vue-test-utils-async for example using vee-vadidate

Or, am I venturing out of the realm of unit test into e2e test here?

@lmiller1990 lmiller1990 changed the title Wait after calling trigger on methods that return promises triggerAsyncmethod for tests that use promises Nov 4, 2017
@lmiller1990 lmiller1990 changed the title triggerAsyncmethod for tests that use promises testing trigger methods that call methods using promises Nov 4, 2017
@eddyerburgh
Copy link
Member

We could add an updateAsync method that can be called with await:

wrapper.trigger('click')

await wrapper.updateAsync()

// wrapper updated

I've been using a library called flush-promises. You can write nice tests in Jest with async/ await that stops you adding setTimeouts inside the tests.

npm install --save-dev flush-promises
import flushPromises from 'flush-promises'

it('sets some text', async () => {
    const wrapper = shallow(App)
    await flushPromises()
    wrapper.find('button').trigger('click')
    await flushPromises()
    expect(wrapper.vm.msg).toBe('testing')
  })

You can see an example here — https://github.com/eddyerburgh/vue-hackernews-chapter-6/blob/master/src/store/__tests__/actions.spec.js#L28

@lmiller1990
Copy link
Member Author

Flush promises looks like it might do nicely. Will try integrate it and see if it solves my problem, and report back.

Have you felt the need or are you happy with just using flushPromises?

@sbarfurth
Copy link

sbarfurth commented Nov 11, 2017

Testing the vee-validate example, flush-promises does not work.

EDIT: It appears to be working now after fixing an issue with babel. It would definitely be nicer to have a built in method for handling Promises though.

@eddyerburgh
Copy link
Member

I don't think vue-test-utils should have a built in way to deal with promises. It's not vue specific, and there are already libraries that flush promises. It's up for discussion, but my opinion is we should use other libraries for it.

Instead we could add a section to the docs.

@sbarfurth
Copy link

There should definitely be a section in the docs, especially since plugins like vee-validate, which uses Promises quite heavily, are popular.

@eddyerburgh eddyerburgh changed the title testing trigger methods that call methods using promises add section to docs on testing components that include promises Nov 11, 2017
@eddyerburgh
Copy link
Member

@lmiller1990 I changed the title to add a section to the docs, if your original issue wasn't solved we can revert the name.

@aphofstede
Copy link

aphofstede commented Nov 14, 2017

Slightly off-topic seeing what was discussed above, but since this is now a docs issue on this subject, I'll comment here.

The docs imply (https://vue-test-utils.vuejs.org/en/guides/getting-started.html#what-about-nexttick) that we don't have to worry about component refreshes, but it seems that things like v-if'ed tags still require the use of something like await flushPromises() for any changes to appear. (even if no promises were actively used)

Example:

    <p v-if="feedback">Your question has been updated.</p>
...
        data () {
            return {
                feedback: false
...
    it ('shows feedback conditionally', async () => {
        expect(wrapper.contains('p')).toBeFalsy()

        wrapper.vm.feedback = true
        await flushPromises()

        expect(wrapper.contains('p')).toBeTruthy()
    });

@eddyerburgh
Copy link
Member

You need to use wrapper.update to update the component synchronously. You should use setData/ setState to update vm data, since they run update for you.

@aphofstede
Copy link

Useful pointers, thanks @eddyerburgh

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

No branches or pull requests

4 participants