-
Notifications
You must be signed in to change notification settings - Fork 672
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 lifecycle hooks mocking #167
Conversation
@eddyerburgh You should have a look at the test and give me some feedback. It uses a for-loop to iterate through all lifecycles and runs the Anyways, the test currently fails, because the |
So the issue was, that the lifecycle-hook |
The last commit passes in my fork repo using circleci. No idea why the package resolution is failing here. |
We'll need to add some info in the docs that using a lifecycle hook name in mocks will overwrite the lifecycle hook |
wrapper.setData({}) | ||
|
||
// call methods that will not be triggered by mount, setData and destroy manually | ||
wrapper.vm.updated() // only called in v2.0.8 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it called in 2.0.8?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to figure out why it does this, or add a warning if a user is using Vue 2.0.x if they try to stub updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it is poorly phrased.
I wanted to simulate behavior that would call each lifecycle-hook exactly once, but my Vue-knowledge is just not deep enough to do so. So I ended up using this combination of mount
, setData
and destroy
to get as many as possible called.
What happened now is that the lifecycle-hooks activated
, deactivated
and updated
were not called, that is why I had to call them manually to verify if the replacing spies are used.
Now in Vue 2.0.8 the updated
method gets called, even if there is no actual change, in > 2.1 it does not get called, I guess it compares the virtual dom to figure out that nothing gets updated and therefore does not call that lifecycle hook. In 2.4.2 (my local version) it did not get called, but in the test:compatibility
it also runs on v2.0.8 and therefore the hook gets called in the test. I still call it 'manually' via wrapper.vm.updated()
, and need to check if it was called once or twice (line 126).
It might be a bit overkill, to keep it simple we could just call all methods directly and check on called
and notCalled
, if you prefer this. It was my first approach as I like to make the tests as specific as possible, therefore the calledOnce
and the poorly-phrased comment. I will expand the comment a bit to make it understanding this issue easier.
I am not sure if the mount(TestedComponent, {
methods: {
mounted() {
console.log('This is the mocked mounted hook!')
}
}
}) |
Can I ask what the use case for this feature is? Test lifecycle hooks... sounds like we are testing Vue, not our own code. I have some components that dispatch in Sorry to chime in late, but I'm interested in the use case behind mocking lifecycle events - I don't see the problem it solves. |
I agree with @lmiller1990 , what is the use case? |
I thought less about testing the lifecycle hooks, but mocking them, replacing them with dumb functions. E. g. when the lifecycle hook might have side effects on objects, that the tested part depends on, and therefore it would be easier to override them, to shut them. Maybe the mounted hook will try to access objects that lead to exceptions, although we want to focus on a completely different part.
Obviously it will be possible to extract the logic in a But I agree, there are not too many use cases and most of the times it can be worked around with a little more work or better design. I just saw the issue popping up several times in issues and forums and found the solution to this after digging just a little. If you think it is unnecessary, feel free to close the PR. If not, tell me, I think I have to rewrite the tests a bit. The way I call the |
That's a valid use case. I think Thanks for your work so far 😄 |
The lifecycle hook test is adjusted to check the hook injection via mixins is not touched by the hook override. Furthermore the way the udpate and updated hooks are called is using the vm.$forceUpdate method now instead of wrapper.setData, as it consistently calls the hooks through all versions. An callback through vm.$nextTick was added to let the update hooks be called, which makes the test an async one using mocha's done method.
Test adjustmentsIt now does not have the exception for the Furthermore I call the lifecycle hooks The test runs the checks for each lifecycle hook in a single test to enhance test performance. To ensure the tester knows which lifecycle hook failed, error descriptions are added. DocumentationI added descriptions to the docs for the mount object, and also a short section in I did not add the description for Looking forward to your suggestions! |
@@ -84,4 +84,65 @@ describe('mount.mocks', () => { | |||
const freshWrapper = mount(Component) | |||
expect(typeof freshWrapper.vm.$store).to.equal('undefined') | |||
}) | |||
|
|||
it('replaces lifecycle hooks, but not mixin hooks with mocks', (done) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you split this into two tests, one for the lifecycle hooks, and one that checks mixin hooks aren't replaced
The two resulting tests are: * test if mocks replace the original lifecycle hooks * test if hooks installed by mixins are still called, if hooks of component are mocked
I don't think we're going to go ahead with this, as we want to avoid editing the internals. Thanks for the PR though 🙂 |
Hello, I have a problem,how do test the funtion called in lifecycle hook like this? import { shallowMount } from '@vue/test-utils'
const CounterDemo = {
template: '<div>{{count}}</div>',
data: () => ({
count: 0,
timer: null,
}),
mounted() {
this.start()
},
destroyed() {
this.stop()
},
methods: {
start() {
this.timer = setInterval(() => {
this.count += 1
}, 1000)
},
stop() {
clearInterval(this.timer)
},
},
}
describe('CounterDemo', () => {
it('test call start when mounted', () => {
// Matcher error: received value must be a mock or spy function ❌
jest.spyOn(CounterDemo.methods, 'start')
const wrapper = shallowMount(CounterDemo)
expect(wrapper.vm.start).toHaveBeenCalledTimes(1)
})
it('test call stop when destroy', () => {
// works well ✅
const wrapper = shallowMount(CounterDemo)
jest.spyOn(wrapper.vm, 'stop')
wrapper.destroy()
expect(wrapper.vm.stop).toHaveBeenCalledTimes(1)
})
}) I want to test start and stop call or not, my test case code is wrong? How can i do better in this case? |
You could just wait for the count to increment - no need to mess about with spy / lifecycle methods. it('test call start when mounted', (done) => {
// Matcher error: received value must be a mock or spy function ❌
const wrapper = mount(CounterDemo)
setTimeout(() => {
expect(wrapper.vm.count).toBe(1)
// or
expect(wrapper.find('div').textContent).toBe("1")
done()
}, 1200)
}) If you don't like waiting, I think Jest has some way to immediately run all timers: https://jestjs.io/docs/timer-mocks |
This solves #166
npm test
passesIt will allow the test author to easily mock lifecycle hooks. Works for all hooks.
Example: