跳至主要内容

模拟 Tauri API

编写前端测试时,通常需要一个“伪造”的 Tauri 环境来模拟窗口或拦截 IPC 调用,这被称为模拟@tauri-apps/api/mocks 模块提供了一些有用的工具来简化此过程。

注意

请记住在每次测试运行后清除模拟,以撤消运行之间的模拟状态更改!请参阅 clearMocks() 文档了解更多信息。

IPC 请求

最常见的是,您希望拦截 IPC 请求;这在各种情况下都很有帮助。

  • 确保进行了正确的后端调用
  • 模拟后端函数的不同结果

Tauri 提供了 `mockIPC` 函数来拦截 IPC 请求。您可以在 此处 详细了解具体的 API。

提示

以下示例使用 Vitest,但您可以使用任何其他前端测试库,例如 Jest。

import { beforeAll, expect, test } from "vitest";
import { randomFillSync } from "crypto";

import { mockIPC } from "@tauri-apps/api/mocks";
import { invoke } from "@tauri-apps/api/tauri";

// jsdom doesn't come with a WebCrypto implementation
beforeAll(() => {
Object.defineProperty(window, 'crypto', {
value: {
// @ts-ignore
getRandomValues: (buffer) => {
return randomFillSync(buffer);
},
},
});
});


test("invoke simple", async () => {
mockIPC((cmd, args) => {
// simulated rust command called "add" that just adds two numbers
if(cmd === "add") {
return (args.a as number) + (args.b as number);
}
});
});

有时您希望跟踪有关 IPC 调用的更多信息;该命令调用了多少次?它是否被调用过?您可以使用 mockIPC() 以及其他间谍和模拟工具来测试这一点。

import { beforeAll, expect, test, vi } from "vitest";
import { randomFillSync } from "crypto";

import { mockIPC } from "@tauri-apps/api/mocks";
import { invoke } from "@tauri-apps/api/tauri";

// jsdom doesn't come with a WebCrypto implementation
beforeAll(() => {
Object.defineProperty(window, 'crypto', {
value: {
// @ts-ignore
getRandomValues: (buffer) => {
return randomFillSync(buffer);
},
},
});
});


test("invoke", async () => {
mockIPC((cmd, args) => {
// simulated rust command called "add" that just adds two numbers
if(cmd === "add") {
return (args.a as number) + (args.b as number);
}
});

// we can use the spying tools provided by vitest to track the mocked function
const spy = vi.spyOn(window, "__TAURI_IPC__");

expect(invoke("add", { a: 12, b: 15 })).resolves.toBe(27);
expect(spy).toHaveBeenCalled();
});

要模拟对侧车或 shell 命令的 IPC 请求,您需要在调用 `spawn()` 或 `execute()` 时获取事件处理程序的 ID,并使用此 ID 来发出后端将发送回的事件。

mockIPC(async (cmd, args) => {
if (args.message.cmd === 'execute') {
const eventCallbackId = `_${args.message.onEventFn}`;
const eventEmitter = window[eventCallbackId];

// 'Stdout' event can be called multiple times
eventEmitter({
event: 'Stdout',
payload: 'some data sent from the process',
});

// 'Terminated' event must be called at the end to resolve the promise
eventEmitter({
event: 'Terminated',
payload: {
code: 0,
signal: 'kill',
},
});
}
});

窗口

有时您有特定于窗口的代码(例如启动画面窗口),因此您需要模拟不同的窗口。您可以使用 mockWindows() 方法创建伪造的窗口标签。第一个字符串标识“当前”窗口(即您的 JavaScript 认为自身所在的窗口),所有其他字符串都被视为附加窗口。

提示

mockWindows() 只伪造窗口的存在,而不是窗口属性。要模拟窗口属性,您需要使用 mockIPC() 拦截正确的调用。

import { beforeAll, expect, test } from 'vitest';
import { randomFillSync } from 'crypto';

import { mockWindows } from '@tauri-apps/api/mocks';

// jsdom doesn't come with a WebCrypto implementation
beforeAll(() => {
Object.defineProperty(window, 'crypto', {
value: {
// @ts-ignore
getRandomValues: (buffer) => {
return randomFillSync(buffer);
},
},
});
});

test('invoke', async () => {
mockWindows('main', 'second', 'third');

const { getCurrent, getAll } = await import('@tauri-apps/api/window');

expect(getCurrent()).toHaveProperty('label', 'main');
expect(getAll().map((w) => w.label)).toEqual(['main', 'second', 'third']);
});