Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

chai-as-promised: multiple expect statements in a single test

I'm using chai-as-promised to test some promises. My issue is I'm not sure how to have multiple expect statements in a single test. In order for the expect().to.be.fulfilled to work properly, I need to return it, like this:

it('test', () => {
  return expect(promise).to.be.fulfilled
}

... or to use notify, like this:

it('test', (done) => {
  expect(promise).to.be.fulfilled.notify(done)
}

The issue comes when I have another thing I need to check, such as that a certain function gets called, like this:

it('test', (done) => {
  var promise = doSomething()
  expect(sinon_function_spy.callCount).to.equal(1)
  expect(promise).to.be.fulfilled.notify(done)
})

The problem here is that, because doSomething() is asynchronous, the call to sinon_function_spy may not have occurred yet when I call that expect, making this test flaky. If I use a then, like this:

it('test', (done) => {
  var promise = doSomething()
  promise.then(() => {
    expect(sinon_function_spy.callCount).to.equal(1)
  })
  expect(promise).to.be.fulfilled.notify(done)
})

Then the test technically passes and fails as expected, but it will fail because the promise gets rejected, due to the thrown exception in the then call. Similarly, if I have a case where the promise is expected to reject:

it('test', (done) => {
  var promise = doSomething()
  promise.then(() => {
    expect(sinon_function_spy.callCount).to.equal(1)
  })
  expect(promise).to.be.rejected.notify(done)
})

Then the check on the sinon_function_spy never gets called, since the promise was rejected and doesn't call then.

How can I get both expect statements to reliably execute and return the correct values?

like image 817
ewok Avatar asked Dec 18 '17 20:12

ewok


3 Answers

If you're using mocha or jest as your test framework you can return the promise with expectations in your then() block:

it('test', () => {
   return doSomething().then( () => {
     expect(sinon_function_spy.callCount).to.equal(1);
   });
});

This test won't end until the promise successfully completes AND the expect has been run. If you're using jasmine you can use the jasmine-promises package to get the same functionality.

For the reverse case, I'd recommend creating a wrapper that reverse the polarity of the promise:

function reverse( promise ) {
   //use a single then block to avoid issues with both callbacks triggering
   return promise.then(
       () => { throw new Error("Promise should not succeed"); }
       e => e; //resolves the promise with the rejection error
   );
}

Now you can do

it('test', () => {
   return reverse( doSomethingWrong() ).then( error => {
       expect( error.message ).to.equal("Oh no");
   });
});
like image 160
Duncan Thacker Avatar answered Nov 15 '22 06:11

Duncan Thacker


A way to achieve multiple expects

it('should fail if no auth', () => {
    const promise = chai.request(server).get('/albums');
    return expect(promise).to.be.rejected.then(() => {
      return promise.catch(err => {
        expect(err).not.to.be.null;
        expect(err.response).to.have.status(401);
        expect(err.response).to.be.a.json;
      });
   });
});
like image 25
fede1608 Avatar answered Nov 15 '22 05:11

fede1608


In the case of wanting to assert that the Promise is fulfilled and a call was performed as expected, you don't really need that first part as an assertion. The mocha test case itself will fail if the Promise rejects as long as you are returning it:

it('test', () => {
  return doSomething()
    .then(() => {
      expect(sinon_function_spy.callCount).to.equal(1)
    });
});

If the Promise returned by doSomething() rejects, so will the test case. If the expect assertion fails, it will also fail the test case with that failed assertion. If you want to be a bit more explicit:

it('test', () => {
  return doSomething()
    .then(() => {
      expect(sinon_function_spy.callCount).to.equal(1)
    }, err => {
      expect(err).to.not.exist;
    });
});

...you can catch the error. Note that with this flavor of then with two callbacks, the assertion failing in the first callback will not reach the second callback, so it'll just be Mocha that sees the failed assertion.

Here's how you can do an expected failed Promise:

it('test', () => {
  return doSomething()
    .then(() => {
      throw new Error('Promise should not have resolved');
    }, err => {
      expect(err).to.exist;
      expect(sinon_function_spy.callCount).to.equal(1)
    });
})
like image 1
Jacob Avatar answered Nov 15 '22 06:11

Jacob