Home > Software engineering >  Unit testing Vue composition functions
Unit testing Vue composition functions

Time:02-04

I'm attempting to write a data-fetching "hook" (not entirely clear what the word for this is in Vue, but a state function that does not render a template). The Hook takes an asynchronous data resolver as a parameter. The hook itself is very simple, it simply adds loading state to a function that returns a promise.

import { ref, watch } from "vue";

export function useDataFetcher<T>(
  resolver: () => Promise<T>,
) {
  const isLoading = ref(false);
  const data = ref<T>();
  watch(resolver, () => {
    isLoading.value = true;
    resolver(...parameters)
      .then((fetchedData) => {
        data.value = fetchedData;
      })
      .finally(() => {
        isLoading.value = false;
      });
  });
  return {
    isLoading: isLoading.value,
    data,
    parameters,
  };
}

I am attempting to write a test against this function to ensure that the isLoading method is updating correctly:

import { useDataFetcher } from "./useDataFetcher";
test("While the fetcher is loading data, isLoading should be true", async () => {
  const promise = new Promise<void>((resolver) =>
    setTimeout(() => resolver(), 2000)
  );
  const { isLoading } = useDataFetcher(() => promise);
  expect(isLoading).toBeTruthy();
  await promise;

  expect(isLoading).toBeFalsy();
});

As written, this test is not working. I have not seen a lot of information in the interwebs about testing these state functions in Vue. There are two stack overflow questions that seem related: Is it possible to test a Vue 3 Composition library which uses inject without a wrapping component?

and How to unit test standalone Vue composition

But neither of these seem to quite scratch the itch I'm having here.

In React, you have the @testing-library/react-hooks library to manage these tests, and it makes it very simple. It seems to me that I'm missing something to the effect of await Vue.nextTick().

So, finally, the question: What exactly is the best way to test these Vue hooks that don't render templates?

CodePudding user response:

You need to return the loading ref to keep the reactivity.

  return {
    isLoading,
    data,
    parameters,
  };

By passing isLoading.value you only pass the value at that time and loose the reactivity

CodePudding user response:

So, I hacked together a solution for my problem. I would love comments on how to make this better, but this is what I've got so far:

Test:

import { mount } from "@vue/test-utils";
import { useDataFetcher } from "./useDataFetcher";
test("While the fetcher is loading data, isLoading should be true", async () => {
  let resolver = () => {};
  const promise = new Promise<void>((resolve) => (resolver = resolve));
  let data: ReturnType<typeof useDataFetcher>;
  const wrapper = mount({
    template: "<div />",
    setup() {
      data = useDataFetcher(() => promise);
    },
  });
  await wrapper.vm.$nextTick();
  expect(data.isLoading.value).toBe(true)
  resolver()
  await promise;
  await wrapper.vm.$nextTick();
  expect(data.isLoading.value).toBe(false)
});

Implementation:

import { onMounted, ref } from "vue";

export function useDataFetcher<T>(
  resolver: () => Promise<T>,
) {
  const isLoading = ref(false);
  const data = ref<T>();
  onMounted(() => {
    isLoading.value = true;
    resolver()
      .then((fetchedData) => {
        data.value = fetchedData;
      })
      .finally(() => {
        isLoading.value = false;
      });
  });
  return {
    isLoading,
    data,
  };
}

Again, not super pretty, but it seems to be working.

  •  Tags:  
  • Related