Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test a function that throws an error asynchronously, using tape?

I am attempting to test this module (receiver.js) for an error thrown:

var request = require('request')

module.exports = function(url){
    request({
        url: url,
        method: 'POST'
    }, function(error) {
        if(error){
            throw error
        }
    })
}

using this test (test.js):

var test = require('tape')

test('Receiver test', function(t){
    var receiver = require('./receiver')
    t.throws(function(){
        receiver('http://localhost:9999') // dummy url
    }, Error, 'Should throw error with invalid URL')
    t.end()
})

but tape runs the assertion before the error is thrown, resulting in the following error message:

TAP version 13  
# Receiver test  
not ok 1 Should throw error with invalid URL  
  ---  
    operator: throws  
    expected: |-  
      [Function: Error]  
    actual: |-  
      undefined  
    at: Test.<anonymous> (/path/to/tape-async-error-test/test.js:5:4)  
  ...  
/path/to/receiver.js:9  
throw error  
^  

Error: connect ECONNREFUSED 127.0.0.1:9999  
    at Object.exports._errnoException (util.js:856:11)  
    at exports._exceptionWithHostPort (util.js:879:20)  
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1062:14)  

Is there a way around this?

like image 949
Lorentz Lasson Avatar asked Feb 01 '16 16:02

Lorentz Lasson


2 Answers

The above example is mostly correct but here's a complete working example that compares async to synchronous side by side and also shows how to check for the error message in a manner similar to the tape examples given on tape's README.md.

test('ensure async function  can be tested to throw', async function(t) {
  // t.throw works synchronously
  function normalThrower() {
    throw(new Error('an artificial synchronous error'));
  };
  t.throws(function () { normalThrower() }, /artificial/, 'should be able to test that a normal function throws an artificial error');

  // you have to do this for async functions, you can't just insert async into t.throws
  async function asyncThrower() {
    throw(new Error('an artificial asynchronous error'));
  };  
  try {
    await asyncThrower();
    t.fail('async thrower did not throw');
  } catch (e) {
    t.match(e.message,/asynchronous/, 'asynchronous error was thrown');
  };
});

like image 184
Paul S Avatar answered Sep 28 '22 18:09

Paul S


Generally, using tape, you have to ensure you call assert.end() after the async call has completed. Using promises (would require request-promise and returning the promise):

test('Receiver test', function(t){
    // Tells tape to expec a single assertion
    t.plan(1);

    receiver('http://localhost:9999')
        .then(() => {
            t.fail('request should not succeed')
        })
        .catch(err => {
            t.ok(err, 'Got expected error');
        })
        .finally({
            t.end();
        });
});

Using async/await:

test('Receiver test', async function(t) {
    try {
        await receiver('http://localhost:9999');
        assert.fail('Should not get here');
    } catch (err) {
        assert.ok(err, 'Got expected error');
    }
    t.end();
});
like image 38
nrabinowitz Avatar answered Sep 28 '22 20:09

nrabinowitz