James Newton
James Newton

Reputation: 7092

Using Mocha, Chai and Sinon to test a delayed action

I want to create a unit test for an asynchronous process. I have created a bare-bones mock up of the situation, using a timeout to delay an alert. (The real process will be the loading of a JS file on the fly).

I'm just beginning with Mocha, Chai and Sinon. I've created a folder called vendor alongside my HTML file. This contains the latest versions of mocha.css, mocha.js, chai.js and sinon.js.

The code below works fine if I comment out the setTimeout() call. How should I alter it so that the sinon.assert... calls will wait for the delayed action to occur?

<!DOCTYPE html>
<head>
  <title>Delayed alert test</title>
</head>

<body>
<div id="mocha"><p><a href=".">Index</a></p></div>
<div id="messages"></div>
<div id="fixtures"></div>
<link rel="stylesheet" type="text/css" href="vendor/mocha.css" />
<script src="vendor/mocha.js"></script>
<script src="vendor/chai.js"></script>
<script src="vendor/sinon.js"></script>

<script>
mocha.setup('bdd')

var spy = sinon.spy(window, 'alert')

describe("Test", function() {
  describe("#alert", function() {
    it("should show an alert", function(done) {
      this.timeout(5000)

      setTimeout(function () { // Works if these 2 lines...
        alert ("Once")
        alert ("Twice")
      }, 2000)                 // are commented out
      sinon.assert.called(spy)
      sinon.assert.calledTwice(spy)
      sinon.assert.calledWithExactly(spy, "Once")
      sinon.assert.calledWithExactly(spy, "Twice")
      done()
    });
  });
})

mocha.run();
</script>
</body>
</html>

Upvotes: 2

Views: 6622

Answers (1)

Matt
Matt

Reputation: 74680

Your assertions and done() (if the test ever got there) are being called as soon as the timeout is setup.

The test sets a timeout

this.timeout(5000)

Schedules your test to run in 2 seconds, and moves on immediately.

setTimeout(...

Checks an assertion that fails

sinon.assert.called(spy)

Then exits before the setTimeout has a chance to run.

The assertions need to run after the setTimeout has completed and as we are in a browser, the asynchronous assertions need to be captured in a try/catch block so the actual exception can be passed back to mocha via done().

Most asynchronous API's allow you to pass a "callback" function in, which is called after the asynchronous tasks completion which is normally the function you put your assertions/done in.

In your case it's a little more literal...

describe("Test", function() {
  describe("#alert", function() {
    it("should show an alert", function(done) {
      this.timeout(5000)

      var stub = sinon.stub(window, 'alert')

      var assertion = function(){
        try {
            sinon.assert.called(stub)
            sinon.assert.calledTwice(stub)
            sinon.assert.calledWithExactly(stub, "Oce")
            sinon.assert.calledWithExactly(stub, "Twice")
            done()
        } catch ( e ) {
            done( e )
        }
      }

      setTimeout(function () {
        alert ("Once")
        alert ("Twice")
        assertion()
      }, 2000)

    });
  });
})

https://jsfiddle.net/6w4p7rxz/

Note, I changed the spy to stub for less clicking. Also use chai! Maybe sinon-chai as well to make things easier to read.

Upvotes: 2

Related Questions