spectron icon indicating copy to clipboard operation
spectron copied to clipboard

Effective testing for IPC events?

Open Aerlinger opened this issue 7 years ago • 13 comments

I'm trying to find a way of effectively testing for IPC events being sent/received on either the main or renderer processes.

Looking at the spectron tests, I came across this: https://github.com/electron/spectron/blob/master/test/application-test.js#L219. However, this approach of assigning a global variable within the main process seems awkward. It also requires modifying the application's code just to enable the test capability for any given IPC event.

I tried the following to no avail:

it("listens for a message on ipcRenderer", function(done) {
    app.client.waitUntilWindowLoaded().then(() => {
      const ipcRenderer = app.electron.ipcRenderer;

      ipcRenderer.on('msg-received', function(evt, payload) {
        payload.should.be("SUCCESS")
        done()
      })

      ipcRenderer.send("msg-received", "SUCCESS")
    })
  });

Evens sent directly from within the renderer process also won't be captured by tests, and I suspect it's because mocha's test process is separate from the process running Electron.

So, is it possible to receive Electron events from either process within a Spectron test, in a similar manner to the example above? I suspect not, but if this is the case, what can be done to enable this capability?

Aerlinger avatar Jul 21 '16 05:07 Aerlinger

ipcRenderer does not send messages to itself, it sends them to ipcMain. I haven't tried this in spectron yet but you could probably do this.

it("listens for a message on ipcRenderer", function(done) {
    app.client.waitUntilWindowLoaded().then(() => {
      app.electron.remote.ipcMain.on('msg-received', function(evt, payload) {
        payload.should.be("SUCCESS")
        done()
      })

      app.electron.ipcRenderer.send("msg-received", "SUCCESS")
    })
  });

However this raises a point about testing as a whole. Considering the IPC framework that electron uses is thoroughly tested. See https://github.com/electron/electron/blob/master/spec/api-ipc-spec.js

Do you actually want / need to test that IPC works correctly. What you need to test is that given a function is called with the correct parameters the correct action occurs. For example.

// This is what you need to test
const doThing = (event, data) => {
  // Do the thing
};

// This is not
ipcRenderer.on('thing', doThing);

You should assume that the framework you are using (Electron) is tested correctly and test your code does what it should, not that IPC does what it should.

This is all just IMO but it makes sense to me 😆

MarshallOfSound avatar Jul 21 '16 05:07 MarshallOfSound

Thanks for your feedback and quick reply.

I realized my error with calling ipcRenderer instead of ipcMain shortly after my post. However, the test still fails with a timeout in your example, so it seems like the 'msg-received' message isn't being intercepted by ipcMain in the test. I'm not totally sure why.

To answer your question, I'm definitely not trying to test the IPC capability of Electron/Spectron directly. I trust those internals work fine as my application code runs as expected. Rather, I'm trying to facilitate integration testing, including IPC, in my own app, and am not sure if or how it can be done using Spectron. Specifically, it would be super helpful to be able to:

  • Dispatch IPC events to the Spectron app (either process) from tests.
  • Listen for events dispatched on either process from the Spectron app.

I suppose the above capabilities could be emulated mocked/stubbed, though it'd be nice to not have to go that route.

Aerlinger avatar Jul 21 '16 05:07 Aerlinger

Spectron is designed for integration testing and is not necessarily ideal for unit testing IPC interactions.

I have done this very successfully using electron-mocha. I inject code into the main process, and run the tests from the renderer process.

Check out my repo to see exactly how I set it up. npm test will run the test suite: https://github.com/colinskow/rx-ipc-electron

colinskow avatar May 07 '17 02:05 colinskow

@Aerlinger did you ever find a solution for listening for the application's IPC events?

@MarshallOfSound listening with app.electron.remote.ipcMain.on does not seem to return any events. We have architected our app with electron-redux and which sends redux actions through IPC.

Being able to listen to ipc would enable our e2e testing to be much more precise when it comes to waiting for certain actions to complete.

The only work around that I can think of here is to spin up a small websocket to communicate between the two processes, but that's a heavy solution to a problem that seems just out of reach on this end, as I can send IPC events to the application without issue

paulius005 avatar Nov 07 '18 13:11 paulius005

ttt

paulius005 avatar Nov 26 '18 03:11 paulius005

I'm also trying to figure this one out. Basically I'd like to be able to implement a helper that listens for an event, then runs a try/catch block to respond back to the event.sender with an appropriate response. I would like to be able to test the helper works in isolation to send out the expected responses, but can't figure out how to generate an Electron Event object without ipcRenderer.send.

Maybe there's another approach I could take to this? I was just hoping to make sure something along these lines could be tested:

async function runAndRespond(
  event: Event, 
  args: object, 
  callback: (args: object) => any, 
  success: string, 
  failure: string
) {
  try {
    const response: any = await callback(args)
    event.sender.send(success, response)
  } catch (e) {
    event.sender.send(failure, e)
  }
}

I have tried this in Jest, just to try and wrap my head around the basic setup:

it('does things', async () => {
  const callback = jest.fn()
  await app.start()
  await app.client.waitUntilWindowLoaded()
  app.electron.remote.ipcMain.on('blah', () => {
    callback()
  })
  expect(callback).toBeCalled()
  app.electron.ipcRenderer.sendSync('blah')
})

But it's failing saying that the Jest mock callback was never called.

Jack-Barry avatar Apr 01 '19 14:04 Jack-Barry

+1

gxbsst avatar Jun 28 '19 07:06 gxbsst

+1

scual avatar Oct 11 '19 10:10 scual

I'm relying on events from ipcMain and ipcRenderer to know when the application is in different states. So I really need to be able to listen to events. Any news on how to achieve this?

ottosson avatar Oct 29 '19 09:10 ottosson

+1

wleev avatar Dec 26 '19 10:12 wleev

I think @colinskow has the right idea here - Spectron seems good for a more high level e2e type of test, like what you would expect to happen from the user perspective. electron-mocha seems more fit to handle the unit testing scenarios discussed here.

@MarshallOfSound is that an accurate assessment?

~~I imagine if~~ If you're using Jest there's an analogue out there as well: jest-electron-runner

Jack-Barry avatar Dec 26 '19 16:12 Jack-Barry

i tried with spectron and mocha but there is no luck . any luck ?? please update ?? i am not able to make the call from ipcRenderer (test/spec.js) to IpcMain (main.js file). spec.js code

it("listens for a message on ipcRenderer", function() {
   this.app.client.waitUntilWindowLoaded().then(() => {
     this.app.electron.ipcRenderer.on('loginCallResult', function(evt, payload) {
         console.log('payload', payload);
       payload.should.include.something.that.deep.equals({username:'[email protected]'})
       //done()
     })

     this.app.electron.ipcRenderer.send("logincall", {
       username: '[email protected]',
       password: 'password'
   })
   });
 });

and main.js file (main processor)

ipcMain.on('logincall', (event, login) => {
    var userName = login.username.toString()
    var password = login.password.toString()
    var result = posAppletInstance.authenticateSync(userName, password);

    event.sender.send('loginCallResult', result.getValueSync());
}) 

can anyone help.

vamsideepak avatar Jul 30 '20 10:07 vamsideepak

I'm guessing the reason none of the "app.mainProcess.on(event, listener)" work is because the arguments are being serialized and sent to the electron process. And functions can't be serialized. However, the documentation doesn't point out this limitation.

I tried this code and got the following error:

await app.mainProcess.on("my-event", () => console.log("got it"))
 error: Could not call remote method 'on'. Check that the method signature is correct. Underlying error: The "listener" argument must be of type function. Received null

It says the function was null.

jameskerr avatar Jan 06 '21 01:01 jameskerr