3

The finally works as described here.

How do I test code that is run in finally?

// code
function doCall(body) {
  isWaitingForRequestToComplete();
  return this.apiService
    .someRequest(body)
    .map(response => transform(response))
    .catch(error => Observable.of('Request failed: ' + error.message))
    .finally(() => {
      requestHasCompleted()
      console.log("Finally");
    });
}

// unit test
it('test observable', () => {
  const testBody = {};
  doCall(testBody).subscribe(() => {
    expect(isWaitingForRequestToComplete).toHaveBeenCalled();
    expect(apiService.someRequest).toHaveBeenCalled();
    console.log("Finally should have been executed");
    expect(requestHasCompleted).toHaveBeenCalled();
  });
});

Output is:

# Finally should have been executed
# Finally
# expect spy requestHasCompleted to have been called

So the finally is called after the subscribe(next) is executed, which makes sense. Putting the expectation in completed: subscribe(next, error, completed), also doesn't help.

Rafael Emshoff
  • 2,541
  • 1
  • 35
  • 47

2 Answers2

4

Lol, autocomplete gave me the answer. On a whim I just put a .after the subscribe() to see if there is any handler for what happens after a subscription, and got the following suggestions: .add, .closed, .remove, .unsubscribe.

.add(function) Adds a tear down to be called during the unsubscribe() of this subscription.

To take the example from this question.

/// code
  source
    .finally(() => console.log('Finally callback'))
    .subscribe(value => console.log('#1 Next:', value), error => console.log('#1 Error:', error), () => console.log('#1 Complete'))
    .add(() => {
      console.log('Executed after finally is called');
    });

/// output
# ...
# Finally calllback
# Executed after finally is called

Although I don't quite understand what triggers the unsubscribe() (http://reactivex.io/rxjs/class/es6/Subscription.js~Subscription.html). I always thought that subscriptions just lie around until they are explicitly unsubscribed from...

Rafael Emshoff
  • 2,541
  • 1
  • 35
  • 47
  • "I always thought that subscriptions just lie around until they are explicitly unsubscribed from..." no it does not. For example take a look to the marble of `of`. It emits what it has to and complete right after the last value. – maxime1992 Nov 23 '17 at 13:40
  • 1
    @Maxime So unsubscribe happens automatically after the `completed` event... also makes sense. Would be great to have some docs somewhere that explains the observable lifecycle. – Rafael Emshoff Nov 24 '17 at 08:28
2

Since you're using it(...) for tests I suppose you're using jasmine or mocha where you can use an optional argument done to test asynchronous functions.

it('test observable', done => {
  this.apiService
    .someRequest(...)
    .map(...)
    .finally(done)
    .subscribe();
})

Notice I used .finally(done) to tell the test environment when this method properly ends. If I didn't call done() the test would fail on timeout.

martin
  • 93,354
  • 25
  • 191
  • 226
  • I didn't have an issue with the test failing due to timeout, but with where to put the `expect()` query, so that the code in `finally()` has already been executed. `done` solves a problem I didn't have. – Rafael Emshoff Nov 24 '17 at 08:26
  • That's the point. You don't need to use `expect` to check whether `finally()` was executed or not. That's what `done` is for. Moreover you can't use `expect` if the Observable chain is asynchronous (for example if you used the `delay` operator). – martin Nov 24 '17 at 08:43