Ok I've got it.
The solution is actually the one proposed by @mido22, but I prefer a slightly different version, which you can derive from the documentation of $scope and inprog.
According to the angular documentation, you need to call $apply() when you are working in asynchronous functions like, it says explicitly, setTimeout.
Here the explanation is better:
code that is being trigger directly as a call back to some external event, from the DOM or 3rd party library, should expect that it is never called from within Angular, and so any Angular application code that it calls should first be wrapped in a call to $apply."
So the solution is to wrap all calls that would affect Angular (including resolve() and reject()) into an $apply.
To avoid inprog errors, one must not call $apply() inside another. It's not the case in this example, but, supposing we had another nested setTimeout, only the last to be called should call $apply().
UPDATE:
according to this, the best way to avoid inprog errors is to wrap the non-angular code inside a $timeout(). It's the strategy recommended by angular guys.
var injector = angular.injector(['ngMock']);
var scope = injector.get('$rootScope').$new();
var q = injector.get('$q');
var promise = function() {
return q(function(resolve, reject) {
setTimeout(function() {
scope.$apply(function() {
resolve();
});
}, 500);
});
};
promise()
.then(function() {
document.getElementById('result').innerHTML = 'TEST RUN';
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.13/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.13/angular-mocks.js"></script>
<p id="result">starting test...</p>