Change Vue 'provide' data during test

Viewed 637

I'm using vue-test-utils@1.1.3 with jest@26 in order to test my component.

The component gets provide data from a parent, and watches for changes of it. Parent component injects a computed value. I want to check that the watcher (and functionality it invokes) works as expected.

I wonder how can I perform a change of the mocked data I provide in the test?

// ParentComponent.vue, using the @vue/composition-api plugin + syntax
setup() {
  let chosenItems = reactive({ items: [] })
  function UpdateItems(items) {
    chosenItems.items = [...items]
  }
  provide(
    "userItems",
    computed(() => chosenItems.items)
  )
  return { UpdateItems }
}
// The component I want to test, using options-api syntax
export default {
  inject: ["userItems"],
  watch: {
    "userItems.value": function (items) {
      // Do Things...
    },
  },
}
// The test code for the component
const wrapper = mount(ComponentToTest, {
  localVue,
  router,
  provide: {
    userItems() {
      return ["mockedItems"]
    },
  },
})
// And now, after mounting, I want to change the data was provided to test the watcher
3 Answers

I could not find another way besides the workaround of calling wrapper.setData({ userItems: ["mockedNewItems"] }).

It will work and will trigger the watcher, but it is as if you're mutating the provided data, which is not ideal.

Alternatively, something that takes a bit more work, but doesn't mutate the provided data, would be to extract this part from ParentComponent.vue:

setup() {
  let chosenItems = reactive({ items: [] })
  function UpdateItems(items) {
    chosenItems.items = [...items]
  }
  provide(
    "userItems",
    computed(() => chosenItems.items)
  )
  return { UpdateItems }
}

to a separate function, which provides it (in a separate file):

export default function provideChosenItems() {
  let chosenItems = reactive({ items: [] });
  function UpdateItems(items) {
    chosenItems.items = [...items];
  }
  provide(
    "userItems",
    computed(() => chosenItems.items)
  );
  return { UpdateItems };
}

Consequently, in your test you can mount the parent component, mock the implementation of the provider function, and then change the data that is provided:

import ParentComponent from "ParentComponent.vue";
import provideChosenItems from "provideChosenItems";
import VueCompositionApi, { provide, ref } from "@vue/composition-api";
import { createLocalVue, mount } from "@vue/test-utils";

const localVue = createLocalVue();
localVue.use(VueCompositionApi);

jest.mock("provideChosenItems");

describe("test", () => {
  it("test", () => {
    const mockedItems = ["mockedItems1"];

    provideChosenItems.mockImplementation(() => {
      provide("userItems", ref(mockedItems));
      return { UpdateItems: () => {} };
    });

    const wrapper = mount(ParentComponent, {
      localVue
    });

    mockedItems.push("mockedItems2");

    // Data is changed, so you can test what happens afterwards,
    // you could do something like
    // wrapper.findComponent(ComponentUnderTest) and assert on stuff
  });
});

Not sure if it works on vue2 composition API, but it should work in vue3 with similar syntax.

The point of the solution is to "provide" the thing that inject expected to receive, which is a reactive reference. Otherwise the is nothing reactive to "watch" in component.

it('test provide watch effect', () => {
  const chosenItems = reactive({ items: ["mockItems"] });
  const items = computed(() => chosenItems.items)

  const wrapper = mount(ComponentToTest, {
    localVue,
    router,
    provide: {
      userItems() {
        return items
      },
    },
  })

  // doing stuff and await for mounting

  chosenItems.items = ["mockItems", "mockItems2"]

  // await flushPromise

  should('watch the change of chosenItems')
})

Since the new reactive system should live along without Vue, you can directly import ref, reactive, computed outside of a vue project, in your case, the test file.

Related