Chimp + chai-as-promised + Meteor

Hi all, we’re using chimp + mocha + chai for acceptance/integration testing of our meteor web app, and, so far, it’s working great. We have some cases where we need to use chai-as-promised, but I’m unclear on how to use promises with server. I’d love to see examples of how to:

  1. Use server.execute, server.call, or server.apply to retrieve document(s) from a Meteor collection.
  2. Use chai-as-promised (expect(documents).to.eventually... or documents.should.eventually) to validate the documents retrieved.
  3. Chain multiple server call promises to test data integrity between multiple documents and collections.

Any suggestions?

I think I solved the problem on my own, with a tip from @sam (via the Xolv.io Community Slack channel). After doing some more experimentation, I figured out I was using .then() incorrectly. It appears there’s no need for a then, since expect(Promise.resolve(newDoc)).to.eventually... automagically blocks execution of subsequent statements until the promise resolves.

Here’s some example code, for future reference:

  it("should get a document and do something with it", function () {
    var newDoc = server.execute(function () {
      var docId = MyCollection.insert({name: 'new document'});
      return MyCollection.findOne({_id: docId});
    });

    expect(Promise.resolve(newDoc)).to.eventually.have.property('_id');
    expect(newDoc.name).to.equal('new document');

    var clonedDoc = server.execute(function (originalDoc) {
      var docId = MyCollection.insert({name: 'copy of ' + originalDoc.name});
      return MyCollection.findOne({_id: docId});
    }, newDoc);

    expect(Promise.resolve(clonedDoc)).to.eventually.have.property('_id');
    expect(clonedDoc.name).to.equal('copy of new document');
  });

Have you tried this:

it("should get a document and do something with it", function () {
    var newDoc = server.execute(function () {
      var docId = MyCollection.insert({name: 'new document'});
      return MyCollection.findOne({_id: docId});
    });

    expect(newDoc).to.have.property('_id');
    expect(newDoc.name).to.equal('new document');

    var clonedDoc = server.execute(function (originalDoc) {
      var docId = MyCollection.insert({name: 'copy of ' + originalDoc.name});
      return MyCollection.findOne({_id: docId});
    }, newDoc);

    expect(clonedDoc).to.have.property('_id');
    expect(clonedDoc.name).to.equal('copy of new document');
  });

Update: I’ve identified a few ways of resolving race conditions in Chimp, without using promises.

  1. Insert an intentional delay in publication methods using Meteor._sleepForMs() to expose tests that are failing due to race conditions. A 7-second delay seems to do the trick.
  2. Wrap problematic asynchronous queries (even server-side ones) with browser.waitUntil.
  3. Use browser.waitForExist or browser.waitForVisible liberally to ensure that subscriptions are complete before proceeding.

Here’s how I’d re-write the code above now:

  it("should get a document and do something with it", function () {
    const longDelay = 60000;

    var newDocId = server.execute(function () {
      return MyCollection.insert({name: 'new document'});
    });

    newDocId.should.be.a('string')
      .and.not.be.empty;

    var newDoc = browser.waitUntil(function () {
      return server.execute(function (docId) {
        return MyCollection.findOne({_id: docId});
      }, newDocId);
    }, longDelay);

    newDoc.should.have.property('name')
      .that.is.a('string')
      .that.equals('new document');

    var clonedDocId = server.execute(function (originalDoc) {
      return MyCollection.insert({name: 'clone of ' + originalDoc.name});
    }, newDoc);

    clonedDocId.should.be.a('string')
      .and.not.be.empty;

    var clonedDoc = browser.waitUntil(function () {
      return server.execute(function (docId) {
        return MyCollection.findOne({_id: docId});
      }, clonedDocId);
    }, longDelay);

    clonedDoc.should.have.property('name')
      .that.is.a('string')
      .that.equals('clone of ' + newDoc.name);
  });

Hope this helps someone. :slight_smile: