electron-userland / spectron

DEPRECATED: 🔎 Test Electron apps using ChromeDriver
http://electronjs.org/spectron
MIT License
1.68k stars 229 forks source link

Effective testing for IPC events? #91

Open Aerlinger opened 8 years ago

Aerlinger commented 8 years ago

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?

MarshallOfSound commented 8 years ago

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 😆

Aerlinger commented 8 years ago

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:

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

colinskow commented 7 years ago

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

paulius005 commented 5 years ago

@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 commented 5 years ago

ttt

Jack-Barry commented 5 years ago

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.

gxbsst commented 5 years ago

+1

scual commented 5 years ago

+1

ottosson commented 4 years ago

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?

wleev commented 4 years ago

+1

Jack-Barry commented 4 years ago

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

vamsideepak commented 4 years ago

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:'admin@ta.com'})
        //done()
      })

      this.app.electron.ipcRenderer.send("logincall", {
        username: 'admin@ta.com',
        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.

jameskerr commented 3 years ago

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.