vue-test-utils doesn't track object props (3)

Created on 25 Aug 2020  路  7Comments  路  Source: vuejs/vue-test-utils

vue-test-utils doesn't track object props (3)

History
This is a copy of my prev issue https://github.com/vuejs/vue-test-utils/issues/1621 and https://github.com/vuejs/vue-test-utils/issues/1651 which was unexpectedly closed by @dobromir-hristov . I have removed delay from mounted, which was simulating fetch delay, and I agree to not mutate component's property inside component and I add jsFiddle https://jsfiddle.net/alexey2baranov/59r34pam/8/ to demonstrate context

Context

i have a user object which is the data model for a vue.js UserComponent. When the component is mounted the user has undefined fields. Then the user is fetched from the server and its name is setted. (see jsFiddle)

Problem

So i found that vue-test-utils doesn't simulate original Vue. Original Vue detects property changing and redraws it's template according new value (see jsFiddle). It doesn't need parent component or event bubbling to do this. But this not works when it runs inside vue-test-utils.

Steps to reproduce

git clone https://github.com/alexey2baranov/vue-test-utils-getting-started
npm test

Expected behaviour

Test should be done

Actual behaviour

Test fails

Possible Solution

Unfortunately i don't know any possible solution. I tried to reset user prop by wrapper.setProp({user: user}) after its fields was fetchad. Unfortunatelly it is impossible as vue-test-utils prevent to set the same object to prop twice.

UPDATED

Great answer and solution from @xanf

Vue internally wraps props into observable, and this is kind of responsibility of new Vue, not the component
Since you're doing mount "manually" just wrap user to Vue.observable(user) (or even entire propsData if you want that) before passing it as propsData to mount.

Most helpful comment

@alexey2baranov Vue internally wraps props into observable, and this is kind of responsibility of new Vue, not the component
Since you're doing mount "manually" just wrap user to Vue.observable(user) (or even entire propsData if you want that) before passing it as propsData to mount.

Since this is unintended side-effect which may "pollute" with Vue other variables I suggest not to include this as default behavior for VTU

All 7 comments

I am surprised that even works, this is definitely not a common use case. Any data Vue is watching and using should be declared in data or something similar.

If you are really desperate to test this you can wrap it, maybe like this.

let foo = 'foo'
const Wrapper = {
  template: `<YourComponent :foo="foo" />
  data() {
    foo
  }
}

const wrapper = mount(Wrapper)
foo = 'bar'
await wrapper.vm.$nextTick()
// do your assertion?

I am also not clear on why setProps is not a viable. This doesn't work?

const Comp = {
  template: `<div>{{ foo }}</div>
  props: {
    foo: {
      default: 'bar'
    }
  }
}

expect(wrapper.html()).toContain('bar')
const wrapper = mount(Comp)
await wrapper.setProps({ foo: 'foo' })
expect(wrapper.html()).toContain('foo')

As for your original jsfiddle, if you are doing things like this you should save the initial value in data. Don't rely on Vue's reactivity for things declared outside of Vue.

@lmiller1990 You are not delegated to decide what is wrong and what is not. Vue don,t interest your mind even if you say hundred times that something is wrong. Vue works like it works. Right?

All you can do is to compare original Vue with vue-test-utils. If we see that original Vue redraws it,s template when user.name=newValue and vue-test-utils doesn,t then we understand that something wrong with vue-test-utils. Instead of that you say "do this , do not do this, create parent component , do not mutate in mounted, don,t relay on" and so on. I say if it works in original Vue, it should works in tests. Isn,t it correct? Isn,t vue-test-utils mission is to test code that works in original Vue?

Your second question about setProps. setProps does not works when you set to prop the same object with mutated property. Don,t you know it? In your code example you set another scalar value so it works. Please don,t say "do as i like again, set new value in test" because there are use cases when we have to keep the same object reference instead of passing new copy of object into component.

So maybe you don,t ban my issue and try to look it?

I have a better idea, make a PR with a fix 馃槒 This is low on the "issue" list, so help is always welcome.

Oh I like this馃憤 This sounds much better. Even if I don,t some others can collaborate around this and make PR.

Unfortunately my knowledge about vue-test-utils and whole Vue is not well enough to do this. Maybe if you don,t ban this issue, somebody else can PR it.

@alexey2baranov Vue internally wraps props into observable, and this is kind of responsibility of new Vue, not the component
Since you're doing mount "manually" just wrap user to Vue.observable(user) (or even entire propsData if you want that) before passing it as propsData to mount.

Since this is unintended side-effect which may "pollute" with Vue other variables I suggest not to include this as default behavior for VTU

This is a copy of my prev issue #1621 and #1651 which was unexpectedly closed by @dobromir-hristov

image
image

"unexpectedly closed"....

As said in all three this is not VTU.

@alexey2baranov @alexey-baranov if you want to make your test pass, you need to convert your user to observable:

  const user = Vue.observable({
      name: "undefined_name",
    });

there's the full test passing:

import Vue from 'vue'
import { mount } from "@vue/test-utils";
import User from "./user";
import flushPromises from "flush-promises/index";

describe("user", () => {
  it("should update rendered html after user.name fetched", async () => {
    const user = Vue.observable({
      name: "undefined_name",
    });

    // mount component that includes user fetching
    const wrapper = mount(User, {
      propsData: {
        value: user,
      },
    });
    await flushPromises();
    expect(wrapper.text()).toContain("undefined_name");

    console.log("emulating fetching user data ...");
    await new Promise((resolve) => setTimeout(resolve, 0));
    user.name = "fetched_name";
    console.log("user updated");

    await flushPromises();
    expect(wrapper.text()).toContain("fetched_name");
  });
});

@xanf

vue internally wraps props into observable, and this is kind of responsibility of new Vue, not the component
Since you're doing mount "manually" just wrap user to Vue.observable(user) (or even entire propsData if you want that) before passing it as propsData to mount.

Oh, thank you very much. That is what i'm looking for. Now I clearly see the difference and know the short way what to do.

Thanks for your replay. And all others. Nice day..

Was this page helpful?
0 / 5 - 0 ratings

Related issues

AustinGil picture AustinGil  路  3Comments

maerteijn picture maerteijn  路  3Comments

dwonisch picture dwonisch  路  3Comments

vilarinholeo picture vilarinholeo  路  3Comments

jonyoder picture jonyoder  路  3Comments