单元测试 Vue 组合函数
Unit testing Vue composition functions
我正在尝试编写一个数据获取“钩子”(不完全清楚 Vue 中的这个词是什么,但是一个不呈现模板的状态函数)。 Hook 将异步数据解析器作为参数。钩子本身非常简单,它只是将加载状态添加到 returns 承诺的函数。
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,
};
}
我正在尝试针对此函数编写测试以确保 isLoading 方法正确更新:
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();
});
正如所写,此测试无效。我没有在网上看到很多关于在 Vue 中测试这些状态函数的信息。
有两个似乎相关的堆栈溢出问题:
和
但这些似乎都不能完全解决我的问题。
在 React 中,你有 @testing-library/react-hooks 库来管理这些测试,这使得它变得非常简单。在我看来,我遗漏了一些 await Vue.nextTick()
.
的效果
最后,问题是:测试这些不呈现模板的 Vue 钩子的最佳方法到底是什么?
您需要 return 加载参考以保持反应性。
return {
isLoading,
data,
parameters,
};
通过传递 isLoading.value
您只传递当时的值并失去反应性
所以,我最终为我的问题整理了一个解决方案并发布了一个 npm 模块:https://www.npmjs.com/package/vue-composable-function-tester。我很乐意提供有关解决方案的反馈。
这是它的外观示例:
测试:
it("Reacts to a resolving promise", async () => {
const resolvedData = {
hello: "world",
};
const promise = Promise.resolve(resolvedData);
const composable = mountComposableFunction(() =>
useAsynchronousLoader(() => promise)
);
await composable.nextTick();
expect(composable.data.isLoading.value).toBe(true);
await promise;
await composable.nextTick();
expect(composable.data.data.value).toStrictEqual(resolvedData);
await composable.nextTick();
expect(composable.data.isLoading.value).toBe(false);
});
实施:
export function useAsynchronousLoader<T>(promiseCreator: () => Promise<T>) {
const isLoading = ref(false);
const data = ref<T>();
const error = ref<object>();
isLoading.value = true;
promiseCreator()
.then((newData) => {
data.value = newData;
})
.catch((e) => {
error.value = e;
})
.finally(() => {
isLoading.value = false;
});
return {
isLoading,
data,
error,
};
}
编辑:改进的代码示例。
我正在尝试编写一个数据获取“钩子”(不完全清楚 Vue 中的这个词是什么,但是一个不呈现模板的状态函数)。 Hook 将异步数据解析器作为参数。钩子本身非常简单,它只是将加载状态添加到 returns 承诺的函数。
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,
};
}
我正在尝试针对此函数编写测试以确保 isLoading 方法正确更新:
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();
});
正如所写,此测试无效。我没有在网上看到很多关于在 Vue 中测试这些状态函数的信息。
有两个似乎相关的堆栈溢出问题:
和
但这些似乎都不能完全解决我的问题。
在 React 中,你有 @testing-library/react-hooks 库来管理这些测试,这使得它变得非常简单。在我看来,我遗漏了一些 await Vue.nextTick()
.
最后,问题是:测试这些不呈现模板的 Vue 钩子的最佳方法到底是什么?
您需要 return 加载参考以保持反应性。
return {
isLoading,
data,
parameters,
};
通过传递 isLoading.value
您只传递当时的值并失去反应性
所以,我最终为我的问题整理了一个解决方案并发布了一个 npm 模块:https://www.npmjs.com/package/vue-composable-function-tester。我很乐意提供有关解决方案的反馈。
这是它的外观示例:
测试:
it("Reacts to a resolving promise", async () => {
const resolvedData = {
hello: "world",
};
const promise = Promise.resolve(resolvedData);
const composable = mountComposableFunction(() =>
useAsynchronousLoader(() => promise)
);
await composable.nextTick();
expect(composable.data.isLoading.value).toBe(true);
await promise;
await composable.nextTick();
expect(composable.data.data.value).toStrictEqual(resolvedData);
await composable.nextTick();
expect(composable.data.isLoading.value).toBe(false);
});
实施:
export function useAsynchronousLoader<T>(promiseCreator: () => Promise<T>) {
const isLoading = ref(false);
const data = ref<T>();
const error = ref<object>();
isLoading.value = true;
promiseCreator()
.then((newData) => {
data.value = newData;
})
.catch((e) => {
error.value = e;
})
.finally(() => {
isLoading.value = false;
});
return {
isLoading,
data,
error,
};
}
编辑:改进的代码示例。