Shouldn't fakeAsync prevent automatic subscription completion? - jasmine

I thought that by using a fakeAsync wrapper, my tests wouldn't automatically run subscriptions and that I'd be controlling that part by calling tick manually, but that doesn't seem to be the case. For example, using this method:
foo(): void {
of([1, 2, 3]).subscribe({
next: () => {
console.info('Subscription completed.')
this.value = true
},
error: (err: unknown) => console.error('error called')
})
}
and testing with this spec:
it('should foo', fakeAsync(() => {
component.foo()
expect(component.value).toBeFalse()
}))
I'm seeing the subscription completed message print and thus the expectation fails. I thought that the foo method would be called, but that the subscription wouldn't complete until I put a tick() call into the spec.
What am I doing wrong?

Your assumption that all observables are asynchronous is wrong.
Observables can either be asynchronous or synchronous depending on the underlying implementation. In other words, reactive streams are synchronous if the source is synchronous unless you explicitly make them asynchronous.
Some examples:
//Synchronous
of(1,2)
.subscribe(console.log);
//asynchronous because of async source
interval(1000)
.subscribe(console.log);
//aynchronous because one stream is async (interval)
of(1,2)
.pipe(
mergeMap(x => interval(1000).pipe(take(2)))
)
.subscribe(console.log);
//async because we make it async
of(1,2, asyncScheduler)
.subscribe(console.log);
TLDR: Because of([1, 2, 3]) is a synchronous source, you won't be able to control when it completes with fakeAsync.

tick is just the way to control the passage of time. from Angular documentation.
If you omit the tickOptions parameter (tick(100))), then tickOptions defaults to {processNewMacroTasksSynchronously: true}.
https://angular.io/api/core/testing/tick

Related

What's more appropriate - concat or map?

I want to execute 2 promises, then pass the result of promise2 to a bunch of other rxjs operators to do a bunch of different things if promise2 resolves. But I want promise2 to execute only if promise1 resolves. To get this effect, should I be using something like
from(promise1).pipe(
map(x => promise2),
tap((x) => console.log('response from map: ', x)),
other_rxjs_ops)
.subscribe(...)
If I use concat instead of map
concat(promise1, defer(() => promise2)).pipe(
tap(console.log('response from map: ', x)),
other_rxjs_ops))
.subscribe(...)
I'm presuming what will happen is that promise1 will execute, and if it resolves, promise2 will execute, and results from both promises will then go through the other rxjs operators and be subscribed to, right?
So is my approach with map for the desired result appropriate or should I be thinking of another rxjs operator to work with in executing promise1 and promise2? The one issue I run into when using map is that the console.log inside tap seems to execute before the promise inside map has resolved or errored out, and console prints the following:
response from map: undefined //presuming promise2 hasn't had a chance to resolve yet, which is why we're getting undefined because the promise is designed to return a string value.
Using concatMap instead of map addresses this issue, but I don't understand why tap behaves the way it does when used with map. Any guidance on that would be helpful also.
About your 1st option
You are using map operator and seeing tap is not waiting for promise to be resolved.
So what is happening here,map operator doesn't subscribe inner observable so it is returning promise object(Promise {}) and will not wait to be resolved,for that tap operator here logging this - response from map: Promise {}
Any higher order mapping(concatMap mergemap switchMap ExhaustMap) subscribe the inner observable.Know more: link1 | Link2
So in this concatMap(x => promise2) of code,concatMap will subscribe promise2 and will emit data when promise2 get resolved.
1st option using concatMap
from(promise1)
.pipe(
concatMap((x) => promise2),
tap((x) => console.log('response from map: ', x))
// other_rxjs_ops...
)
.subscribe();
In your case source observable which is from(promise1) will emit response once promise1 is resolved.After receiving response from promise1,concatMap will subscribe promise2
One Promise can emit only one data either resolve or reject so you can use mergeMap or any higher order map operator also here.
So you can consider 1st option if you need to use promise1 response to call promise2.
Now coming to your 2nd option,which is using concat.
So you can consider this over 1st option if promise2 is not dependent on response of promise1.
Simply your requirement is to call promise2 after completion of promise1
I am providing a working example,by which you can clear your doubts
import { concat, from, interval, of } from 'rxjs';
import {
concatMap,
exhaustMap,
map,
mergeMap,
switchMap,
take,
tap,
} from 'rxjs/operators';
const source = interval(1000).pipe(take(5));
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise1 resolved!');
// reject('Promise1 rejected!');
}, 5000);
});
/*..............Option 1..............*/
source
.pipe(
mergeMap((result) => {
console.log("Inside pipe: ",result);
return promise1;
})
// concatMap(()=>promise1),
)
.subscribe({
next: console.log,
error: console.error,
complete: () => console.log('Completed!'),
});
// source.pipe(
// tap(console.log),
// map(()=>promise1),
// ).subscribe(console.log);
/*..............Option 2..............*/
concat(source, promise1).subscribe({
next: console.log,
error: console.error,
complete: () => console.log('Completed!'),
});
In your case, you can use one of the Higher-Order RxJS mapping operators, such as concatMap, mergeMap, or switchMap, to merge streams to each other.
You can try something like the following:
from(promise1)
.pipe(
// promise2 won't be executed until the promise1 is resolved.
concatMap((val1) => from(promise2)),
// tap won't be executed until the promise2 is resolved.
tap((val2) => console.log('response from promise2:', val2))
// Other RxJS operators here...
)
.subscribe();
Notes:
The map operator is used to transform the value to another type, and in your case, it's used to transform the promise1 result to a Promise, however, it doesn't subscribe to it nor convert it to an Observable, so the tap is executed directly after transforming it, without waiting it to be resolved.
(Optional) To be able to subscribe to the promise1, it should be transformed to Observable using from function.
To subscribe to the new Observable it should be merged with the original from(promise1) one, using one of the higher-order mapping operators not using the normal map.

Expecting a Promise *not* to complete, in Jest

I have the following need to test whether something does not happen.
While testing something like that may be worth a discussion (how long wait is long enough?), I hope there would exist a better way in Jest to integrate with test timeouts. So far, I haven't found one, but let's begin with the test.
test ('User information is not distributed to a project where the user is not a member', async () => {
// Write in 'userInfo' -> should NOT turn up in project 1.
//
await collection("userInfo").doc("xyz").set({ displayName: "blah", photoURL: "https://no-such.png" });
// (firebase-jest-testing 0.0.3-beta.3)
await expect( eventually("projects/1/userInfo/xyz", o => !!o, 800 /*ms*/) ).resolves.toBeUndefined();
// ideally:
//await expect(prom).not.toComplete; // ..but with cancelling such a promise
}, 9999 /*ms*/ );
The eventually returns a Promise and I'd like to check that:
within the test's normal timeout...
such a Promise does not complete (resolve or reject)
Jest provides .resolves and .rejects but nothing that would combine the two.
Can I create the anticipated .not.toComplete using some Jest extension mechanism?
Can I create a "run just before the test would time out" (with ability to make the test pass or fail) trigger?
I think the 2. suggestion might turn handy, and can create a feature request for such, but let's see what comments this gets..
Edit: There's a further complexity in that JS Promises cannot be cancelled from outside (but they can time out, from within).
I eventually solved this with a custom matcher:
/*
* test-fns/matchers/timesOut.js
*
* Usage:
* <<
* expect(prom).timesOut(500);
* <<
*/
import { expect } from '#jest/globals'
expect.extend({
async timesOut(prom, ms) { // (Promise of any, number) => { message: () => string, pass: boolean }
// Wait for either 'prom' to complete, or a timeout.
//
const [resolved,error] = await Promise.race([ prom, timeoutMs(ms) ])
.then(x => [x])
.catch(err => [undefined,err] );
const pass = (resolved === TIMED_OUT);
return pass ? {
message: () => `expected not to time out in ${ms}ms`,
pass: true
} : {
message: () => `expected to time out in ${ms}ms, but ${ error ? `rejected with ${error}`:`resolved with ${resolved}` }`,
pass: false
}
}
})
const timeoutMs = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); })
.then( _ => TIMED_OUT);
const TIMED_OUT = Symbol()
source
The good side is, this can be added to any Jest project.
The down side is, one needs to separately mention the delay (and guarantee Jest's time out does not happen before).
Makes the question's code become:
await expect( eventually("projects/1/userInfo/xyz") ).timesOut(300)
Note for Firebase users:
Jest does not exit to OS level if Firestore JS SDK client listeners are still active. You can prevent it by unsubscribing to them in afterAll - but this means keeping track of which listeners are alive and which not. The firebase-jest-testing library does this for you, under the hood. Also, this will eventually ;) get fixed by Firebase.

How to modify an Effect to work with multiple requests called?

I've written this Effect to handle one call at a time:
#Effect()
indexCollectiveDocuments$ = this.actions$.pipe(
ofType<IndexCollectiveDocuments>(CollectiveIndexingActionTypes.IndexCollectiveDocuments),
mergeMapTo(this.store.select(getIndexingRequest)),
exhaustMap((request: any[], index: number) => {
return zip(...request.map(item => {
this.currentItem = item;
return this.indexingService.indexDocuments(item).pipe(
map((response: any[]) => new IndexCollectiveDocumentsSuccess(response)),
catchError(error => of(new IndexCollectiveDocumentsFailure({ error: error, item: this.currentItem })))
)
}))
})
);
It does dispatch both Success and Failure actions according to the request result.
But when I feed the effect with multiple items(which are the payload of getIndexingRequest) to send requests one after another, the Success and Failure actions are not dispatched accordingly, cancels when one is failed.
How do I modify it so that it works with multiple requests, rather than one?
EDIT: I can see all the requests and their results in the network tab. But only one action is dispatched.
You're using the RxJS operator exhaustMap which will cancel incoming requests when there is already one request running.
To run all requests use either mergeMap (run parallel) or concatMap (run serial).

Using asynchronous Nightwatch After Hook with client interaction does not work

As far as I can tell, using promises or callbacks in After hook prevents Command Queue from executing when using promises / callbacks. I'm trying to figure out why, any help or suggestions are appreciated. Closest issue I could find on github is: https://github.com/nightwatchjs/nightwatch/issues/341
which states: finding that trying to make browser calls in the after hook is too late; it appears that the session is closed before after is run. (exactly my problem). But there is no solution provided. I need to run cleanup steps after my scenarios run, and those cleanup steps need to be able to interact with browser.
https://github.com/nightwatchjs/nightwatch/wiki/Understanding-the-Command-Queue
In the snippet below, bar is never outputted. Just foo.
const { After } = require('cucumber');
const { client } = require('nightwatch-cucumber');
After(() => new Promise((resolve) => {
console.log('foo')
client.perform(() => {
console.log('bar')
});
}));
I also tried using callback approach
After((browser, done) => {
console.log('foo');
client.perform(() => {
console.log('bar');
done();
});
});
But similar to 1st example, bar is never outputted, just foo
You can instead use something like:
const moreWork = async () => {
console.log('bar');
await new Promise((resolve) => {
setTimeout(resolve, 10000);
})
}
After(() => client.perform(async () => {
console.log('foo');
moreWork();
}));
But the asynchronous nature of moreWork means that the client terminates before my work is finished, so this isn't really workin for me. You can't use an await in the perform since they are in different execution contexts.
Basically the only way to get client commands to execute in after hook is my third example, but it prevents me from using async.
The 1st and 2nd examples would be great if the command queue didn't freeze and prevent execution.
edit: I'm finding more issues on github that state the browser is not available in before / after hooks: https://github.com/nightwatchjs/nightwatch/issues/575
What are you supposed to do if you want to clean up using the browser after all features have run?
Try the following
After(async () => {
await client.perform(() => {
...
});
await moreWork();
})

Observables: Complete vs finally vs done

When speaking about Observables (especially rxjs), what is the difference between "finally" and "done" or "complete"?
Finally always happens whenever an observable sequence terminates (including errors); completed only happens when it terminates without errors.
Finally:
Invokes a specified action after the source observable sequence
terminates gracefully or exceptionally.
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/finally.md
OnCompleted:
An Observable calls this method after it has called onNext for the
final time, if it has not encountered any errors.
http://reactivex.io/documentation/observable.html
"Done" isn't an rx/observables concept. I've just seen it printed in examples of "Complete" / "OnComplete".
Note: when you call subscribe, the syntax is usually:
observable.subscribe([observer] | [onNext], [onError], [onCompleted]);
// Like this:
observable.subscribe(
(value) => { ... },
(error) => { ... },
() => { console.log('complete!'); }
);
or
observable.subscribe({
next: x => console.log('got value ' + x),
error: err => console.error('something wrong occurred: ' + err),
complete: () => console.log('done'),
});
Whereas finally is handled like this:
observable.finally(() => { console.log('finally!'); })
.subscribe(...) // you can still call subscribe
To be more precise, the finally() operator adds a dispose handler. The complete notification just calls the complete handler in observers.
What this means in practise:
When using finally() the callback is going to be called in every situation that causes unsubscription. That's when complete and error notifications are received by observers but also when you manually unsubscribe.
See demo: https://jsbin.com/dasexol/edit?js,console
complete or error handlers are called only when the appropriate notification is received. Only 0 - 1 handlers can be called but never both of them.

Resources