How can be unit tested this Observable?
e1.pipe(
concatMap(x => of(x).pipe(withLatestFrom(e2)))
);
Following unit test fails:
it('test', () => {
const e1 = hot( '--1^---2----3-|');
const e2 = hot( '-a-^-b-----c--|');
const expected = cold( '----x----y-|', {
x: ['2', 'b'],
y: ['3', 'c']
});
const result = e1.pipe(
concatMap(x => of(x).pipe(
withLatestFrom(e2))
)
);
// but this works:
// const result = e1.pipe(withLatestFrom(e2));
expect(result).toBeObservable(expected);
});
How the marbles should be written in order to pass this unit test? What did I do wrong?
I expect by inserting concatMap
operator in the chain (before withLatestFrom
) I have to also somehow "mark" it in the marbles.
In your real example
e1.pipe(
concatMap(x => of(x).pipe(withLatestFrom(e2)))
);
everything works fine probably because is either a BehaviorSubject
or a ReplaySubject
, which it's not case in your test.
Although you're using hot( '-a-^-b-----c--|');
, it does not imply that you're using a BehaviorSubject
. If we look at the implementation, we'll see that HotObservable
extends the Subject
class:
export class HotObservable<T> extends Subject<T> implements SubscriptionLoggable { /* ... */ }
which should help understand why this works:
const result = e1.pipe(withLatestFrom(e2));
and this doesn't:
const result = e1.pipe(
concatMap(x => of(x).pipe(
withLatestFrom(e2))
)
);
In the first snippet, e2
is subscribed when e1
is subscribed. In the second one, because you're using concatMap
, every time e1
emits, withLatestFrom(e2))
will be subscribed and then unsubscribed, due to the complete
notification that comes from of(x)
.
With this in mind, here would be my approach:
Note: I'm using the built-in functions provided by rxjs/testing
it('test', () => {
// might want to add this in a `beforeEach` function
let testScheduler = new TestScheduler(
(actual, expected) => (console.log({actual, expected}),expect(actual).toEqual(expected))
);
testScheduler.run(({ hot, expectObservable }) => {
const e1 = hot( '--1^---2----3-|');
const e2src = hot( '-a-^-b-----c--|');
const e2 = new BehaviorSubject(undefined);
const result = e1.pipe(
concatMap(x => of(x).pipe(
withLatestFrom(e2))
)
);
const source = merge(
result,
e2src.pipe(
tap(value => e2.next(value)),
// this is important as we're not interesting in `e2src`'s values
// it's just a way to `feed` the `e2` BehaviorSubject
ignoreElements()
)
);
expectObservable(source).toBe('----x----y-|', {
x: ['2', 'b'],
y: ['3', 'c']
});
});
})