-
Notifications
You must be signed in to change notification settings - Fork 668
remove setComputed #331
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
Comments
Is using One of my main use cases I feel is might be related is when I have computed functions like this: all () {
return this.$store.state.members.all
},
allIds () {
return this.$store.state.members.allIds
}, then I need to do import { createLocalVue, shallow } from 'vue-test-utils'
import Vuex from 'vuex'
const localVue = createLocalVue()
localVue.use(Vuex)
let store
beforeEach(() => {
store = new Vuex.Store({
state: {
members: {
all: {}, allIds: []
}
}
}
})
// write some test to see if a component is rendered by looping `allIds`,
// and another to see what happens if there if `allIds` is empty... Which is a huge amount of boilerplate. So I normally do:
which saves making the Vuex store. I believe if you are just doing data tests, which involved seeing how the UI looks based on some state, you should not use an actual store, but simply Do you have an alternative in mind that would allow for a computed value to be changed? Or is |
I think better persist setComputed For example, if we have 3 computed variables: a, b, c When we write tests for |
Was some good discussion in this thread a while back. #55 (comment) |
@Alex-Sokolov I don't think you should test a computed prop like that in a component unit test, it's essentially testing implementation details instead of testing the result of the component. If on a rare ofccation a computed prop is so complicated that they need to be tested individually, do that outside of a component unit test and test the function on its own. As I agree with the risks @eddyerburgh mentions (I brought them up, in fact), I vote to remove it. |
If removed, will we still be able to set shallow(Foo, {
computed: {
someVal: () => true
}
}) ? |
Yes @lmiller1990 |
Ok then I am happy to see |
「namespace: trueなstore」と「そうでないstore」のどちら側に定義されたcomputedなのか を意識できておらずハマった。 この場合、itemsInCartという算術プロパティ(computed)は、rootStoreに定義されたプロパティである。 mockedStoreではmockできないので、別で渡している。 ``` const mockedStore = { shoppingModule: { namespaced: true, getters: { cartProducts: jest.fn().mockReturnValue([ { name: 'a', price: 1, quantity: 10 }, { name: 'b', price: 2, quantity: 20 }, { name: 'c', price: 3, quantity: 30 }, ]), }, }, } console.log(mockedStore) const store = new Vuex.Store(mockedStore) // 今現在、itemsInCartは名前空間で別れていないので、namespaced: trueなmockedStoreではmockできない // TODO: 今後いい方法を考える const wrapper = shallow(NavBar, { store, localVue, mocks: { $route }, computed: { itemsInCart: jest.fn().mockReturnValue(10)}}) ``` 下記リンクが解決に繋がった [参考] vuejs/vue-test-utils#331
@LinusBorg 'Complicated' computed variables in my case are fairly simple methods but they can rely on a fairly long chain of dependencies on other computed variables. Testing them separately would be overkill. It's not clear how the original concern of setComputed providing a permanent mock is not addressed by clear docs as opposed to removing it completely. It seems like there's only downsides to this. |
Don't remove Suppose I have the following component:
When testing, I want to ensure that
|
Mocking computed is dangerous, because you can put your component into a state that it can't be in during production. The concern with You could achieve the same result by overwriting the computed option when you mount the component: const wrapper = mount(Foo, {
computed: {
bar() {
return 'baz'
}
}
}) |
Isn't setComputed just a helper for overwriting the computed option more
tersely?
On Thu, 15 Mar 2018, 08:25 Edd Yerburgh, ***@***.***> wrote:
Mocking computed is dangerous, because you can put your component into a
state that it can't be in during production. The concern with setComputed
is that it encourages users to mock the computed value.
You could achieve something similar by overwriting the computed option.
const wrapper = mount(Foo, {
computed: {
bar() {
return 'baz'
}
}
})
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#331 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AGGoAC5pDMDi0F2R3woz6ef_VKviREO1ks5teiWIgaJpZM4RUVmq>
.
--
sent from phone (possible typos)
|
No, it's overwriting internals of the component to return something different than the compued property returned. That's a bit different from replacing the computed property with a stub, especially since that has to happen during mount, while statically replacing the computed property upfront is much more transparent. |
Fair enough, thanks for clarifying. |
I've tried a lot of messing around with overwriting the computed option at mount, but none of it seems to trigger the watcher. I think it's because we specifically need to test that the computed property changes after the mount ---- we are getting the computed property from VueX state which can change after the component is mounted with its initial state from the Vuex store. Also, why have a setData and take out setComputed? |
@yx296 I think the difference between |
@lmiller1990 exactly |
So there is anyway else besides setComputed to mock changing a computed property after the initial mount? Without having to mock out the entire VueX store? I think this is why setComputed be should left in--- a unit test for a Vue component watcher shouldn't have to involve mocking out the entire VueX Store. |
yah i need to mock a computed value after mount. |
@jorySsense @yx296 can you explain what you are trying to achieve (including the context)? Maybe there is a better way to do so. What situation causes you to what to change a computed value after mount in a unit test? |
Example component: export default {
computed: {
isDone: function () {
return this.$store.getters['isDone']
}
},
watch: {
isDone: function () {
if (this.isDone) {
// do something
}
}
}
} I could not come up with a solution/setup to trigger the watcher. Already mounting the component with the computed value set to true will not trigger the watcher. For example: wrapper = mount(MyComponent, {
store,
localVue,
computed: {
isDone: function () { return true }
}
}) does not work, because it does not trigger the watcher. |
Exactly, If i haven't fully mocked my store and don't want to dispatch commits to trigger updates. The setComputed was extremely usefull. |
@lmiller1990 , I dont understand your point about production. If you're mocking methods, whats the difference? |
Thanks for the example @DennisSchmidt . This might help @jorySsense and @yx296 as well. There are two things to test here:
I assume you are okay to test the first one. Let's talk about the second thing on the list. You are using
But what you really want to do is point 3 (and only point 3). There are some ways to do so, without using To do this, one technique that I find useful is using Here is an example: import Component from './component.vue'
describe('watch', () => {
it('does something', () => {
const localThis = {
someValue: 'foo'
}
Component.watch.isDone.call(localThis)
// assertion that something was done
})
}) This is pretty cool. You don't even need to bother with Here is a more solid example. Let's say your data() {
return {
count: 0
}
},
watch: {
isDone: function() {
this.count += 1
}
} When import Compnent from './component.vue'
it('increases', () => {
const localThis = { count: 0 }
Component.watch.isDone.call(localThis)
// note we did not use render. we are doing export default in the component, so we just get a plain old JavaScript object to play with.
expect(localThis.count).toBe(1)
}) Cool, right? Super compact, no You probably will have a situation that actually does impact the markup though. Here is how to handle that situation: <template>
<div>{{ count }}</div>
</template>
<script>
export default {
data() {
return { count: 0 }
}
},
watch: {
isDone: function() {
this.count += 1
}
}
</script> Ok, now we need a it('increases', () => {
const wrapper = shallow(Component)
wrapper.vm.$options.watch.isDone.call(wrapper.vm) // we can access the components `this` from `wrapper.vm`
expect(wrapper.text()).toBe("1")
}) Great! We could trigger it, without knowing or caring about where or what The main takeaway here is unit tests should be isolated. If one method relies on another to trigger something in order to run the test, it is not a unit test anymore. I made you a full working example of using I hope this helps. Let me know if you didn't understand something, or have some input. |
Thats awesome! thank you |
@lmiller1990 @eddyerburgh that takes care of testing the watcher, but suppose you want to directly use the variable from the state? Here's an example:
A unit test for this would look like:
Seems like a perfectly legitimate case for |
@jshor you can still use the describe('MyFoo', () => {
it('should show foo--present', () => {
const wrapper = shallowMount(Foo, {
computed: {
foo() { return true }
}
})
expect(wrapper.find('foo--present').exists()).toBe(true)
expect(wrapper.find('foo--hidden').exists()).toBe(true)
})
}) You can scroll through this thread and read about why this is different to A good rule of thumb is try to declare the state of you component up front, when you mount/shallowMount it. It can help make your tests more clear. This is just my personal preference though, based on my own experience. Hopefully this can help you test your Vue app! PS: if you are using |
@lmiller1990 that will work, but restricts you to having to What if I had several unit tests that varied based on the value of the computed property, but everything else stayed the same? In that case I would no longer be able to write a one-liner that changed the property in that particular unit test--I would instead have to re-create the wrapper each time, and not in a |
An example to my above comment regarding the utilization of the describe('MyFoo', () => {
let wrapper
beforeEach(() => {
const wrapper = shallowMount(Foo)
})
it('should show foo--present', () => {
wrapper.setComputed({ foo: true })
expect(wrapper.find('foo--present').exists()).toBe(true)
expect(wrapper.find('foo--hidden').exists()).toBe(false)
})
it('should hide foo--present', () => {
wrapper.setComputed({ foo: false })
expect(wrapper.find('foo--present').exists()).toBe(false)
expect(wrapper.find('foo--hidden').exists()).toBe(true)
})
}) the equivalent when using the describe('MyFoo', () => {
it('should show foo--present', () => {
const wrapper = shallowMount(Foo, {
computed: {
foo () { return true }
}
})
expect(wrapper.find('foo--present').exists()).toBe(true)
expect(wrapper.find('foo--hidden').exists()).toBe(false)
})
it('should hide foo--present', () => {
const wrapper = shallowMount(Foo, {
computed: {
foo () { return false }
}
})
expect(wrapper.find('foo--present').exists()).toBe(false)
expect(wrapper.find('foo--hidden').exists()).toBe(true)
})
}) |
@jshor You can use a factory function like here. In your case, it might be something like this: const factory = (computed = {}) => shallowMount(Foo, { computed })
describe('MyFoo', () => {
it('should show foo--present', () => {
const wrapper = factory({ foo: () => true })
expect(wrapper.find('foo--present').exists()).toBe(true)
expect(wrapper.find('foo--hidden').exists()).toBe(false)
})
it('should hide foo--present', () => {
const wrapper = factory({ foo: () => true })
expect(wrapper.find('foo--present').exists()).toBe(false)
expect(wrapper.find('foo--hidden').exists()).toBe(true)
})
}) You can customize the arguments to Neither the style you presented (using The reason removing The reason passing a |
I must say I came here to see how to trigger a watch after I have mocked out a Vuex getter. I wrote an issue about this some time ago. #456 but that didnt go anywhere. |
I think manually triggering it is okay - you want to test the logic in the watcher, not to see if the watcher updates, usually. You already know the watcher will trigger, since that is part of Vue. Maybe an e2e test would be a good place to see if the component is behaving correctly when the component triggers the watcher. Each to their own, though. Each app and team is different, I'm definitely guilty of writing a lot of unit tests by not enough e2e tests. |
For those like me who desperately needs this feature, you can use this helper function. It does the same what /**
* Applies hard-coded computed properties to a Vue instance.
* The code took from a deprecated `wrapper.setComputed`.
* @see https://github.com/vuejs/vue-test-utils/issues/331
*/
export default function setComputed (wrapper, computed) {
if (!wrapper.isVueInstance()) {
throw new Error('setComputed() can only be called on a Vue instance')
}
Object.keys(computed).forEach(key => {
if (!wrapper.vm._computedWatchers[key]) {
throw new Error(
'setComputed() was passed a value that does not exist as a computed ' +
'property on the Vue instance. Property ' + key + ' does not exist ' +
'on the Vue instance'
)
}
wrapper.vm._computedWatchers[key].value = computed[key]
wrapper.vm._computedWatchers[key].getter = () => computed[key]
})
wrapper.vm._watchers.forEach(watcher => {
watcher.run()
})
} For those who use Vue <= v2.1, see the contents of |
The setComputed method changes the value of computed properties permanently.
That means the instance is in a state that is impossible to reach in production. This could lead to bugs that slip through tests because a component behaves differently in tests versus production.
What do people think?
The text was updated successfully, but these errors were encountered: