First, be aware that done()
implies a synchronous test; Mocha's default is to run tests asynchronously. If you want to test the 'returned' value from asynchronous functions (functions that return a value in a callback function), you run them synchronously, via done()
.
Next, you can't return a value from an asynchronous function. These two behaviours are mutually exclusive:
function returnAndCallback(callback) {
callback(5); // not always invoked
return 3;
}
You want to only execute the callback.
It appears to me that you're expecting that sometimes a callback is passed, but not always. In that case, I'd separate the function tests (I think you'll need to use done()
everywhere, to persist the synchronous nature of the tests) and do a check for callback inside the function itself.
Now that we've got that clarified, since you want to assert that a callback is called, we need to establish some baseline assumptions:
- A) A callback should be a function
- B) A callback should be called with a parameter that contains a value
You want to test for both of these things. A) is easy to prove: you're writing the callback function as part of your test, so if you passed say, null
or undefined
, of course the test will fail, but that's not the point of this test. Here is how you prove both A) and B):
function optionalAsync(callback) {
if (typeof callback === 'function') {
callback(4)
} else {
return 3
}
}
describe('optional async function', function() {
it('should return 4 when a callback is passed', function(done) {
optionalAsync(function(value) {
should(value).be.eql(4)
done()
})
})
it('should return 3 when no callback is passed', function(done) {
optionalAsync().should.be.eql(3)
done()
})
})
This is kind of strange, but given your use case, it does make sense to check for both possibilities. I'm sure you could reduce the code footprint a bit too, but I'd suggest keeping it this way for the sake of readability for when you shelve tis for a year and forget what you did ;)
Now after all of this if you still want to have the option for a function to run synchronously you can do so by blocking the event loop: https://stackoverflow.com/a/22334347/1214800.
But why would you want to?
Save yourself the trouble of handling synchronous operations in an inherently non-blocking, asynchronous platform, and write everything (even the non-IO-blocking operations) with a callback:
function optionallyLongRunningOp(obj, callback) {
if (typeof callback === 'function') {
validateObject(obj, function(err, result) {
// not always long-running; may invoke the long-running branch of your control-flow
callback(err, result)
})
} else {
throw new Error("You didn't pass a callback function!")
}
}
describe('possibly long-running op async function', function() {
it('should return 4 when control flow A is entered', function(done) {
obj.useControlFlow = "A"
validateObject(obj, function(err, result) {
// this is a slow return
should(result.value).be.eql(4)
done()
})
it('should return 3 when control flow B is entered', function(done) {
obj.useControlFlow = "B"
validateObject(obj, function(err, result) {
// this is a quick return
should(result.value).be.eql(3)
done()
})
})
})
Here is your answer written with everything as callback (even the short ops):
var doLongRunnignOp = function(cb) {
var didNotify = true
cb(didNotify)
}
function doubleAndNotifyEven(num, cb) {
if (num % 2 == 0) {
doLongRunnignOp(function(didNotify) {
cb(num)
// did notify
})
} else {
cb(2 * num)
// instant return, did not notify
}
}
describe('checking return value and callback execution', function() {
it('should double and report given an even number', function() {
doubleAndNotifyEven(2, function(value) {
should(value).be.eql(2)
})
})
it('should double and not report anything given an odd number', function() {
doubleAndNotifyEven(3, function(value) {
should(value).be.eql(6)
})
})
})