Typescript 对Spectron中的主要事件作出反应
我有一个electron应用程序,它首先启动一个启动程序窗口(在渲染器进程中),启动几个后台服务。成功启动这些后台服务后,它将其Typescript 对Spectron中的主要事件作出反应,typescript,electron,spectron,ipcmain,Typescript,Electron,Spectron,Ipcmain,我有一个electron应用程序,它首先启动一个启动程序窗口(在渲染器进程中),启动几个后台服务。成功启动这些后台服务后,它将其IPC渲染器上的“正在运行的服务”发送回主进程,主进程通过关闭启动器窗口并启动主应用程序窗口对该事件作出反应。当然,ipcMain.on('services-running',…) 我分别对所有处理程序进行了单元测试,因此这些处理程序都很好,现在我想对通过ipcMain的事件进行集成测试 这就是我的集成测试目前的情况: import { Application } fr
IPC渲染器上的“正在运行的服务”
发送回主进程,主进程通过关闭启动器窗口并启动主应用程序窗口对该事件作出反应。当然,ipcMain.on('services-running',…)
我分别对所有处理程序进行了单元测试,因此这些处理程序都很好,现在我想对通过ipcMain
的事件进行集成测试
这就是我的集成测试目前的情况:
import { Application } from 'spectron';
import * as electron from "electron";
import { expect } from 'chai';
import * as chai from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
let app: Application;
global.before(() => {
app = new Application({
path: "" + electron,
args: ["app/main.js"],
env: {
ELECTRON_ENABLE_LOGGING: true,
ELECTRON_ENABLE_STACK_DUMPING: true,
NODE_ENV: "integrationtest"
},
startTimeout: 20000,
chromeDriverLogPath: '../chromedriverlog.txt'
});
chai.use(chaiAsPromised);
chai.should();
});
describe('Application', () => {
before('Start Application', () => {
return app.start();
});
after(() => {
if(app && app.isRunning()){
return app.stop();
}
});
it('should start the launcher', async () => {
await app.client.waitUntilWindowLoaded();
return app.client.getTitle().should.eventually.equal('Launcher');
});
it('should start all services before timing out', async (done) => {
console.log('subscribed');
app.electron.remote.ipcMain.on('services-running', () => {
done();
});
});
});
第一个测试运行良好。虽然在主窗口弹出之前,我可以在shell上看到subscribed
,但在达到超时后,第二个测试最终将失败,因此事件肯定会触发
我在文档中读到需要启用nodeIntegration
才能使用spectron访问完整的electron api,我的所有渲染器进程都是在各自的webPreferences
中使用{nodeIntegration:true}
启动的。但是由于我对主进程感兴趣,我认为这不适用(或者至少我认为不应该,因为主进程本身就是一个节点进程)
所以我的主要问题是,我如何绑定到ipcMain
事件,并将这些事件包含在我的断言中。还有,我如何知道启动器窗口何时关闭,“主”窗口何时打开
作为奖励,我对spectron api有一些理解上的问题
如果我看一下spectron.d.ts
应用程序的electron
属性属于electron.allegectron
类型,它又是main接口
,直接具有ipcMain
属性。因此,在我的理解中,访问ipcMain
应该是app.electron.ipcMain
(未定义),远程设备来自何处,为什么它在spectron.d.ts
中不可见
SpectronClient
上的方法全部返回Promise
。所以我必须等待
或然后
这些。如果我看一下javascript示例,它们会链接客户机语句:
这在typescript中不起作用,因为您无法链接到
承诺
显然,。。。这在js中是如何工作的?因此我分别解决了这些问题。我将所有内容迁移到类中,并使用字段/构造函数注入将所有依赖项放入类中,这样我就可以模拟它们,包括来自electron的内容
export class LauncherRenderer implements Renderer {
protected mongo: MongoProcess;
protected logger: Logger;
protected ipc: IpcRenderer;
protected STATUS_LABEL: string = 'status-text';
constructor() {
this.ipc = ipcRenderer;
this.mongo = new MongoProcess(this.ipc);
this.logger = new Logger('launcher', this.ipc);
}
在类中,我在订阅事件时将始终使用this.ipc
。对于单元测试,我有一个FakeIpc
类:
import { EventEmitter } from 'events';
export class FakeIpc {
public emitter: EventEmitter = new EventEmitter();
public send(channel: string, message?: any): void { }
public on(event: string, listener: () => void): void {
this.emitter.on(event, listener);
}
public emit(event: string): void {
this.emitter.emit(event);
}
}
为启动器渲染器设置单元测试时,我将FakeIpc
注入渲染器:
beforeEach(() => {
fakeIpc = new FakeIpc();
spyOn(fakeIpc, 'on').and.callThrough();
spyOn(fakeIpc, 'send').and.callThrough();
mongoMock = createSpyObj('mongoMock', ['start', 'stop', 'forceStop']);
underTest = new LauncherRenderer();
underTest.mongo = mongoMock;
underTest.ipc = fakeIpc;
});
这样,如果订阅已经完成,我就可以监视ipc,或者使用publictrigger
方法来触发ipc事件,并测试我的类是否正确响应它
对于我认识到的集成测试,我不应该关心内部事件(这是在单元测试中完成的),只关心这些事件的结果(窗口关闭和打开)。像这样:
it('should start the launcher', async () => {
await app.client.waitUntilWindowLoaded();
const title: string = await app.client.getTitle();
expect(title).toEqual('Launcher');
});
在下一个测试中,我会等到启动器消失,一个新窗口打开,这样,事件一定会发生,否则就不会发生
it('should open main window after all services started within 120s', async () => {
let handles: any = await app.client.windowHandles();
try {
await Utils.waitForPredicate(async () => {
handles = await app.client.windowHandles();
return Promise.resolve(handles.value.length === 2);
}, 120000);
await app.client.windowByIndex(1);
} catch (err) {
return Promise.reject(err);
}
const title: string = await app.client.getTitle();
expect(title).toEqual('Main Window');
});
waitForPredicate
仅仅是一个助手方法,它在到达超时后等待承诺来解析或终止测试
public static waitForPredicate(
predicate: () => Promise<boolean>,
timeout: number = 10000,
interval: number = 1000,
expectation: boolean = true): Promise<void> {
return new Promise<any>(async (res, rej) => {
let currentTime: number = 0;
while (currentTime < timeout) {
// performance.now() would be nicer, but that doesn't work in jasmin tests
const t0: number = Date.now();
const readyState: boolean | void = await predicate().catch(() => rej());
if (readyState === expectation) {
res();
return;
}
await Utils.sleep(interval);
const t1: number = Date.now();
currentTime += t1 - t0;
}
// timeout
rej();
});
}
public static sleep(ms: number): Promise<void> {
if (this.skipSleep) {
return Promise.resolve();
}
return new Promise<void>((res) => setTimeout(res, ms));
}
公共静态waitForPredicate(
谓词:()=>承诺,
超时:数字=10000,
间隔:数字=1000,
期望值:布尔值=真):承诺{
返回新承诺(异步(res,rej)=>{
设currentTime:number=0;
while(当前时间<超时){
//performance.now()会更好,但这在jasmin测试中不起作用
常量t0:number=Date.now();
const readyState:boolean | void=wait谓词().catch(()=>rej());
如果(readyState==期望值){
res();
回来
}
等待效用。睡眠(间隔);
常数t1:number=Date.now();
电流时间+=t1-t0;
}
//超时
rej();
});
}
公共静态睡眠(ms:number):承诺{
如果(这个跳过睡眠){
返回承诺。解决();
}
返回新承诺((res)=>setTimeout(res,ms));
}
您在这方面运气好吗?我也试着去听一些主要的事件…@格言我把我的变通方法作为一个答案。我认为这是一个关于如何处理模仿内部库内容的通用解决方案。从那时起,我们就一直在使用这种方法,在其他语言的一些项目中也使用了这种方法,目前它仍然有效。太好了,非常感谢!只是一个关于waitForPredicate的问题。它看起来像一个整洁的实用程序。你如何实现它?您是否以某种方式使用setInterval()检查谓词?@motoson我添加了它:)超级!再次感谢!=)
public static waitForPredicate(
predicate: () => Promise<boolean>,
timeout: number = 10000,
interval: number = 1000,
expectation: boolean = true): Promise<void> {
return new Promise<any>(async (res, rej) => {
let currentTime: number = 0;
while (currentTime < timeout) {
// performance.now() would be nicer, but that doesn't work in jasmin tests
const t0: number = Date.now();
const readyState: boolean | void = await predicate().catch(() => rej());
if (readyState === expectation) {
res();
return;
}
await Utils.sleep(interval);
const t1: number = Date.now();
currentTime += t1 - t0;
}
// timeout
rej();
});
}
public static sleep(ms: number): Promise<void> {
if (this.skipSleep) {
return Promise.resolve();
}
return new Promise<void>((res) => setTimeout(res, ms));
}