cujojs / when

A solid, fast Promises/A+ and when() implementation, plus other async goodies.
Other
3.44k stars 396 forks source link

Testing with `Promise.done()` #428

Closed OJezu closed 9 years ago

OJezu commented 9 years ago

I have a scenario in which all I can do upon an error is crash the process (or pass to something that knows what to do with such things). I'm using Promise.done() for that. Problem is, I would like to unit test if in that scenario .done() is called, but without crashing unit test process. So before hacking around in prototypes I would like to ask:

Is there (or should be introduced) a "supported" way to do such testing?

OJezu commented 9 years ago

I found (via debugging api doc) I can set when.Promise.onFatalRejection. Problem solved, but I think when.Promise.onFatalRejection could get more exposition.

rkaw92 commented 9 years ago

Dnia 13 lutego 2015 16:40:52 CET, Christopher Chrapka notifications@github.com napisał(a):

Closed #428.


Reply to this email directly or view it on GitHub: https://github.com/cujojs/when/issues/428#event-235455881

Hi,

This might end rather funky if you separate the unit tests from the main application, or do cross-module requires.

The way that require() caching works is that it will store a single copy of the module in its internal module map, indexed by name. However, if two copies are installed in different locations, even if one is a symlink to the other, you may find that the when module that you're monkey-patching is not the same object instance that the "when" your app is using. That is, it may be hard to prevent a split-brain module situation and as such relying on globals is not recommended. I have had a case of duplicate "when" modules in an application personally.

A better approach may be exposing an EventEmitter from your object/code and calling .emit('error') in .done()'s rejection callback. If no handler is registered, the process shall crash. Your tests can then call .on() and prevent this.

OJezu commented 9 years ago

@rkaw92 You know, mocha has something like after(). So...

it('should crash process', function(d){
    var org_onFatalRejection = when.Promise.onFatalRejection;
    after(function(){
        when.Promise.onFatalRejection = org_onFatalRejection;
    });
    when.Promise.onFatalRejection = function(rejection){
        try{
            rejection.value.should.be.instanceOf(Errors.FatalError);
            d();
        } catch(err){
            d(err);
        }
    };
    (...)
});
briancavalier commented 9 years ago

@OhJeez, yes indeed, that is a fairly typical way to test something like onFatalRejection. However, I think I might go for a more direct approach. If you simply assume that done has been implemented correctly in when.js--that is, assume that if it's called, then it will indeed crash the process--then all you need to do is spy on done. Testing that onFatalRejection is called is probably too fine grained of an implementation detail to test.

For example, you could simply mock/spy/override when.Promise.prototype.done, and then verify that it was called.

@rkaw92's point is a good one, though, and worth expanding on a bit. In a node app, it's entirely possible for multiple copies of when.js (or any package, for that matter) to be in play (via transitive dependencies). Each copy would have it's own when.Promise constructor with its own onFatalRejection property. It's just important to keep in mind that by overriding one of them, you won't be overriding them all. That's why it's important not to rely on being able to override it for all cases.

That's the impetus behind the new process events in node and window events in browsers in when >= 3.7.0. If all copies of when.js in play are emitting those, then its possible to observe them centrally. Other promise implementations have also added those events, as well. So, as long as all the copies of all the promise implementations in your app do that, it's possible to centrally observe all unhandled rejections.

As of right now, though, there's no 'fatalRejection` event.

OJezu commented 9 years ago

@briancavalier I tried overwriting when.Promise.prototype.done, but I had strange results. It had old behaviour (not-ovewritten) when tests were run, unless I used mocha --watch in which case, on second run it indeed used the overwritten method. I think that prototype (or methods) must be copied at some point, and I didn't feel like searching through when code to see what is exactly happening.

OJezu commented 9 years ago

Crap, [Reopen and comment] button is awfully big.