Sinon: Testing Sync/Async environments

Created on 22 May 2017  路  7Comments  路  Source: sinonjs/sinon

  • Sinon version : ^1.17.6
  • Environment : node + mocha + sinon

What did you expect to happen?
Test properly Sync/Async environments with mocha + fakeXML from sinon

What actually happens
No request

How to reproduce

'use strict';
/* eslint-env mocha, node */
/* global XMLHttpRequest */
const assert = require('assert');
const sinon = require('sinon');

//---------------------------------------------------------------------------//
// The function being tested
//---------------------------------------------------------------------------//
function mySyncXhr(url, done){
    const request = new XMLHttpRequest();
    request.onreadystatechange = () => {
        if (request.readyState === XMLHttpRequest.DONE){
            let error = false;
            let response = false;
            if (request.status === 200){
                response = request.responseText;
            } else {
                error = new Error('Something failed');
            }
            done(error, response);
        }
    };
    request.open('GET', url, false);
    request.send();
}

function myAsyncXhr(url, done){
    const request = new XMLHttpRequest();
    request.onreadystatechange = () => {
        if (request.readyState === XMLHttpRequest.DONE){
            let error = false;
            let response = false;
            if (request.status === 200){
                response = request.responseText;
            } else {
                error = new Error('Something failed');
            }
            done(error, response);
        }
    };
    request.open('GET', url);
    request.send();
}
//---------------------------------------------------------------------------//


describe('Fake XHR', () => {
    let server;
    before(() => {
        server = sinon.fakeServer.create();
    global.XMLHttpRequest = sinon.useFakeXMLHttpRequest();
    });
    after(() => {
        server.restore();
    });

    it('Test Async', () => {
        const callback = sinon.spy();

        assert.strictEqual(server.requests.length, 0, 'No request yet');
        myAsyncXhr('/fake/example.json', callback);
        assert.strictEqual(server.requests.length, 1, 'Only one request');
        assert.strictEqual(server.requests[0].url, '/fake/example.json');

        server.requests[0].respond(200, {'Content-Type': 'text/plain'}, 'Hello World');
        sinon.assert.calledOnce(callback);
        sinon.assert.calledWith(callback, false, 'Hello World');
    });

    it('Test Sync', () => {
        const callback = sinon.spy();

        assert.strictEqual(server.requests.length, 0, 'No request yet');
        mySyncXhr('/fake/example.json', callback);
        assert.strictEqual(server.requests.length, 1, 'Only one request');
        assert.strictEqual(server.requests[0].url, '/fake/example.json');

        server.requests[0].respond(200, {'Content-Type': 'text/plain'}, 'Hello World');
        sinon.assert.calledOnce(callback);
        sinon.assert.calledWith(callback, false, 'Hello World');
    });
});

All 7 comments

Delete this line, and it will work fine:

global.XMLHttpRequest = sinon.useFakeXMLHttpRequest();

The fake server already installs the fake XHR. Also, you never assign the global XMLHttpRequest object yourself. Sinon already does it. Like with the fake server, you're given back the Sinon API to control it, not the replacement. server.restore() is then reverting the change and sets XMLHttpRequest back to the original value.

But without this line there is an error:

  1) Fake XHR Test Async:
     ReferenceError: XMLHttpRequest is not defined
      at myAsyncXhr (test/unit/helpers.spec.js:108:22)
      at Context.it (test/unit/helpers.spec.js:140:3)

Ah, you're probably using this in Node. There is no XMLHttpRequest object in Node. This is a browser API. Sinon works for both platforms, but this feature only make sense in the context of a browser.

I am running the test via phantomJS browser, works for sync call, but no for async

'use strict';
/* eslint-env mocha, node */
/* global XMLHttpRequest */
var assert = require('assert');
var sinon = require('sinon');

var test = 1;

//---------------------------------------------------------------------------//
// The function being tested
//---------------------------------------------------------------------------//
function myXhr(url, callback, async){
    var request = new XMLHttpRequest();
  console.log('Async');
    request.onreadystatechange = function () {
        if (request.readyState === XMLHttpRequest.DONE){
      console.log('Done');
            var error = false;
            var response = false;
            if (request.status === 200){
        console.log('Ok', test);
                response = request.responseText;
            } else {
                error = new Error('Something failed');
            }
            callback(error, response);
        }
    };
    request.open('GET', url, async);
    request.send();
}
//---------------------------------------------------------------------------//


describe('Fake XHR', function () {
    var server;
    before(function () {
        server = sinon.fakeServer.create();
    });
    after(function () {
        server.restore();
    });

    it('Test Async', function () {
        var callback = sinon.spy();

        assert.strictEqual(server.requests.length, 0, 'No request yet');
        myXhr('/fake/example.json', function (err, res) {
      assert.strictEqual(test, 0);
      callback(err, res);
    }, true);
    test = 0;
        assert.strictEqual(server.requests.length, 1, 'Only one request');
        assert.strictEqual(server.requests[0].url, '/fake/example.json');

        server.requests[0].respond(200, {'Content-Type': 'text/plain'}, 'Hello World');
        sinon.assert.calledOnce(callback);
        sinon.assert.calledWith(callback, false, 'Hello World');
    });

    it('Test Sync', function () {
        var callback = sinon.spy();

        assert.strictEqual(server.requests.length, 0, 'No request yet');
        myXhr('/fake/example.json', function (err, res) {
      assert.strictEqual(test, 1);
      callback(err, res);
    }, false);
        assert.strictEqual(server.requests.length, 1, 'Only one request');
        assert.strictEqual(server.requests[0].url, '/fake/example.json');

        server.requests[0].respond(200, {'Content-Type': 'text/plain'}, 'Hello World');
        sinon.assert.calledOnce(callback);
        sinon.assert.calledWith(callback, false, 'Hello World');
    });
});

Html:

<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>TextChanger tests</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.4.1/mocha.css"/>
</head>
<body>
    <div id="mocha"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.4.1/mocha.min.js"></script>
    <script>mocha.setup('bdd')</script>
    <script src="./tests-browserify.js"></script>
    <script>
        if (window.mochaPhantomJS) {
            mochaPhantomJS.run();
        } else {
            mocha.run();
        }
    </script>
</body>
</html>

Thank you for putting up a complete example. I can look into this tomorrow.

I am able to run the 'Sync' part using

  • 'expect' instead of 'sinon.assert'
  • server.respondWith(...)
  • server.respondImmediately = true;

But with those things i am able to run it, but i lost the spy and the request assertions

I understand what is going on there now. There are a few things to clean up first:

  • Your tests influence each other because you're not setting up a fresh fake server for each test. You need to change before and after to beforeEach and afterEach.
  • You shouldn't have a shared variable, in this case var test, that is changed and tested in different tests. You should always make sure to reset any variables in beforeEach to avoid unwanted side effects.

Once these things are fixed, you can observe that the synchronous call fails because the fake server immediately responds with a 404. This makes sense because a synchronous call must return immediately in order to mimic the synchronous nature. The Sinon fake server does not know about the requested URL and instantly replies with 404.

The way to fix the tests is by pre-defining the response with server.respondWith. Here a cleaned up and working version of your tests:

    describe("#1417", function () {

        function myXhr(url, callback, async) {
            var request = new XMLHttpRequest();
            request.onreadystatechange = function () {
                if (request.readyState === XMLHttpRequest.DONE) {
                    var error = false;
                    var response = false;
                    if (request.status === 200) {
                        response = request.responseText;
                    } else {
                        error = new Error("Unexpected status " + request.status);
                    }
                    callback(error, response);
                }
            };
            request.open("GET", url, async);
            request.send();
        }

        var server;
        beforeEach(function () {
            server = sinon.fakeServer.create();
        });
        afterEach(function () {
            server.restore();
        });

        it("Test Async", function () {
            var callback = sinon.spy();

            assert.strictEqual(server.requests.length, 0, "No request yet");

            myXhr("/fake/example.json", callback, true);
            assert.strictEqual(server.requests.length, 1, "Only one request");
            assert.strictEqual(server.requests[0].url, "/fake/example.json");

            server.requests[0].respond(200, {"Content-Type": "text/plain"}, "Hello World");
            sinon.assert.calledOnce(callback);
            sinon.assert.calledWith(callback, false, "Hello World");
        });

        it("Test Sync", function () {
            server.respondWith("GET", "/fake/example.json", [200, {
                "Content-Type": "text/plain"
            }, "Hello World"]);
            var callback = sinon.spy();

            assert.strictEqual(server.requests.length, 0, "No request yet");

            myXhr("/fake/example.json", callback, false);
            assert.strictEqual(server.requests.length, 1, "Only one request");
            assert.strictEqual(server.requests[0].url, "/fake/example.json");

            sinon.assert.calledOnce(callback);
            sinon.assert.calledWith(callback, false, "Hello World");
        });
    });

If you have usage questions in the future, consider posting on the mailing list. Thank you.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

byohay picture byohay  路  3Comments

NathanHazout picture NathanHazout  路  3Comments

JakobJingleheimer picture JakobJingleheimer  路  3Comments

ljian3377 picture ljian3377  路  3Comments

stephanwlee picture stephanwlee  路  3Comments