Correct Way to Test Asynchronous IPC calls with Jasmine


#1

I was wondering if anyone has experience of the correct way to use Jasmine’s done() method of testing asynchronous calls, with respect to the ipc.send and ipc.on parts of main - renderer communication.

It’s how to use the done() method in the beforeEach() of the test which is tripping me up

I can test ipc.sendSync() fine with the below

// within main process
ipc.on('marco', function(event, arg){
    event.returnValue = 'polo';
});
// within specs
describe("ipc synchronous message received", function(){

  it('on receiving immediate send message', function(){
    expect(ipc.sendSync('marco', 'ping')).toBe('polo');
  })
});

when writing the test for asynchronous events the below consistently fails, the inclusion of done() doesn’t change the order of execution in order that the assertion will pass

// within main process
ipc.on('marco polo', function(event, arg){
    event.sender.send('polo', true);
})

// within specs
describe('ipc asynchronous test', function(){

  var value = false;

  beforeEach(function(done) {

    ipc.on('polo', function(arg){
      value = arg;
    });
  });

  it('on receiving asyncreturn of ipc send request', function(done){
    ipc.send('marco polo',function(){
      console.log('sending the ipc message'); // never printed anyway
    });
    console.log("\nafter the ipc send");
    expect(value).toBe(true); // the assertion
    done();
  });
});

I have tried numerous variations on above but I have had no success, does anyone have suggestions for where I’m going wrong?


#2

I’m not an async expert, but it appears that this may be where you’re going wrong:

ipc.send('marco polo',function(){
  console.log('sending the ipc message'); // never printed anyway
});
console.log("\nafter the ipc send");
expect(value).toBe(true); // the assertion

It doesn’t appear to me that there is anywhere that you’re yielding control for the async part to happen. It’s as if you’re ordering your dinner at a restaurant and then before the waiter has a chance to walk away from the table to put in your order with the kitchen, you ask them where your dinner is.

In the example on the Jasmine site for async specs, there is some code that looks like a similar flow to what you’re attempting:

http://jasmine.github.io/1.3/introduction.html#section-31

You’ll notice there is a “runs → waitFor → runs” construct in there. This is what, to my understanding, yields control to allow the async magic to do its thing.


#3

Thanks again for the really prompt reply!

So I’m using Jasmine v2.2 and unfortunately they deprecated the runs --> waitFor --> runs construct, which is frustrating as it was more explicit (and more straightforwardly comprehended) in terms of how the asynchronous process proceeded.

I’ve updated the code based on your suggestions and I believe that the logging that I’ve included illustrates that the done() is invoked and yields control to the beforeEach done()

// within main process
ipc.on(‘marco’, function(event, arg){
console.log(arg+": polo?");
event.sender.send(‘polo’, true);
});

// within specs
    describe('asynchronous IPC test', function(){

  var value = false;
  //jasmine.getEnv().defaultTimeoutInterval = 10000;

  beforeEach(function(done) {
    // if you put the before each within the it() declaration then all the tests break
    ipc.on('polo', function(arg){
      console.log("\ninside beforeEach\nasync received so `value` should be true: "+arg);
      value = arg;
      done(); // done should locate within the callback
    });
    //done(); // place done here to see Jasmine test return 'NO SPEC ASSERTIONS ERROR'
  });

  it('on receiving async any given ipc send request', function(done){

    console.log("\nbefore the ipc.send");
    ipc.send('marco', 'marco'); // test that the main process is receiving args passed
    done(); // it is unclear if assertions after done are detected, docs do not follow this flow
    console.log("\nafter the ipc send, value = "+value);
    expect(value).toBe(true);
    console.log('after the assertion'); // should not print as it's after the assertion
  });
});

I’m tempted to revert my Jasmine back to a per 2.0 version, but would prefer to understand why I can’t get the current spec to pass. The above code will fail with async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL but I honestly believe that to be a read herring.


#4

Completely understandable and generally the right idea :smile:

Ah ok, I get it now. But the JavaScript syntax is making me go cross-eyed, so I’m going to explain my thoughts in pseudo-code. My understanding is you want to test that value is true after the async message is received. So here is how I would do it:

  • In beforeEach
    1. Set up the ipc.on that sets value to equal true and then calls done
    2. Call ipc.send to send the message that will be received by the ipc.on
  • In it
    1. Check that value is equal to true
    2. Call done

Does that help? This is all complicated by the fact that you’re trying to test something executing in two contexts when you really only have one context to work with, the Jasmine specs. Does it work if you just put it all in the same file? Like can you have an ipc.on in the renderer that receives an ipc.send that is sent by the renderer?


#5

I haven’t tested ipc communication between two renderer files, but that is definitely the next step!

So with the aid of a fresh set of eyes the following solution was devised, that doesn’t avail of a beforeEach(). However I suspect what you described should work

This is the passing test now

// main code is the same as preceding examples

// passing test code

describe('asynchronous IPC test', function(){


  it('on receiving async any given ipc send request', function(done){
    var value; // this value will be successfully changed when the function returns

    ipc.on('polo', function(arg){
      value = arg;
      expect(value).toBe(true);
      done(); // essential
    });
    ipc.send('marco', 'marco');
    // the placement of this method in flow of execution doesn't matter, however putting it here helps with the idea that
    // a listener (ipc.on) should be established before sending messages to the `main` process
  });
});

On re-reading the Jasmine documentation it turns out I erred in thinking that including a done() necessitated the inclusion of a beforeEach() - as the docs say:

Calls to beforeEach, it, and afterEach can take an optional single argument that should be called when the async work is complete.

So passing a done() to a single definition is enough. It might make sense to use a beforeEach() for larger files, but I generally think the readability of the test is improved when its all within the same it() block.

Thanks again for the help, I’ll report back on the outcomes of your suggestions and the inter-renderer use of ipc when I write the tests, figure it’s helpful to have it all under this forum post for posterity’s sake