3

I'm running a test using mocha and sinon to get a callback value from inside a promise scope of HTTP-request and it doesn't work due to the async nature of promises. It's because by the time sinon.spy checks on callback, It would have been vanished already and become empty or undefined. Here's the testing code:

 it('should issue GET /messages ', function() {
  server.respondWith('GET', `${apiUrl}/messages?counter=0`, JSON.stringify([]));
  let callback = sinon.spy();
  Babble.getMessages(0, callback);
  server.respond();
  sinon.assert.calledWith(callback, []);
});

and the promise:

function requestPoll(props) {
    return new Promise(function(resolve, reject) {
            var xhr = new XMLHttpRequest();
            xhr.open(props.method, props.action);
            xhr.timeout = 500; // time in milliseconds
            if (props.method === 'post' ) {
                    xhr.setRequestHeader('Content-Type', 'application/json');
            }
            xhr.addEventListener('load', function(e) {
                    resolve(e.target.responseText);
            });

            xhr.send(JSON.stringify(props.data));


    });
}

and the call which I'm trying to get callback from on sinon.spy

getMessages: function(counter, callback){

            requestPoll({

                            method: "GET",
                            action: "http://localhost:9090/messages?counter="+counter

            }).then(function(result){

                    callback(result);
            });


        }

sinon.spy says it didn't have any arguments (due to async functionality). I tried to look for a way to get result outside the scope and put it on callback.yet I found out it was impossible. also tried resolve and promise return but didn't succeed.

How can I make this unit test pass?

Edit:
this is my attempt:

getMessages: function(counter, callback){

            var res;
            res = httpRequestAsync("GET",'',"http://localhost:9097/messages?counter=",counter);

            console.log(res);
            if(res!="")
                    callback( JSON.parse(res) );                      
        }

I put the request in a separate function:

function httpRequestAsync(method,data,theUrl,counter)
    {

            return requestPoll({

                    method: method,
                    action: theUrl+counter,
                    data: data

            }).then(JSON.parse);

    }

It returned res as the promise and inside its prototype there's the promised value I need. enter image description here

How can I access that promised value over there?

Shaked
  • 185
  • 1
  • 9
  • Make `getMessages` return a proper promise instead of taking a callback, that will lead to both better and easier testable code. – Bergi Aug 05 '17 at 21:01
  • Can you show me? I tried return promise a couple of times. @Bergi what do I need to write under "then" to return a proper promise? – Shaked Aug 06 '17 at 09:45
  • You just omit the `then`, and put a `return` in front of your expression. Please show us your attempt. – Bergi Aug 06 '17 at 12:45
  • @Bergi I added my attempt to the question. How can I return the expression right? – Shaked Aug 06 '17 at 14:25
  • Drop all the lines except the `return requestPoll({…});`, and remove the `callback` parameter. If you need to parse the result, add `.then(JSON.parse)` to that. Then change all calls to use the promise instead of passing a callback. – Bergi Aug 06 '17 at 15:03
  • I really do need to pass a callback. Is there an option to do so with the callback parameter for the test to pass? – Shaked Aug 06 '17 at 15:22
  • No, you really don't. Just write `getMessages(5).then(thecallback)` where you call this function. – Bergi Aug 06 '17 at 16:08
  • @Bergi Well I did more or less as you said and put it in a separate request function. I had already tried this before I posted this question. Now I have the same problem as before. How can I access the prototype promised value? I understood it was impossible. – Shaked Aug 07 '17 at 08:32
  • See Yury's answer below. You should not put it in a separate function, you should just use `getMessagesAsync` that returns a promise. You can easily use that with an asynchronous test then. – Bergi Aug 07 '17 at 15:37

1 Answers1

2

I recommend you not to mix promises and callbacks. If you already have promise based function stick with it

First make getMessages not to break promise chaing. Make it return a Promise

getMessages: function(counter) {
  return requestPoll({
    method: "GET",
    action: "http://localhost:9090/messages?counter=" + counter
  }).then(JSON.parse)
}

Then use this promise in your test

it('should issue GET /messages ', function() {
  server.respondWith('GET', `${apiUrl}/messages?counter=0`, JSON.stringify([{testdata}]));
  const gettingMessages = Babble.getMessages(0);
  server.respond();

  // return a promise so testing framework knows the test is async
  return gettingMessages.then(function(messages) {
      // make assertion messages is actually testdata
  })
})
Yury Tarabanko
  • 44,270
  • 9
  • 84
  • 98