I try to test a simple angular component using a marble test. For that I'm using the TestScheduler
which comes together with rxjs.
Here is a stackblitz link with the code: https://stackblitz.com/edit/angular-ivy-xwzn1z
This is a simplified version of my component:
@Component({
selector: 'detail-component',
template: ` <ng-container *ngIf="(detailsVisible$ | async)"> <p> detail visible </p> </ng-container>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DetailComponent implements OnInit, OnDestroy {
@Input() set isAdditionalContentVisible(isAdditionalContentVisible: boolean) {
this.resetSubject.next(isAdditionalContentVisible);
}
private readonly resetSubject = new Subject<boolean>();
private readonly toggleVisibilitySubject = new Subject<void>();
private readonly destroySubject = new Subject();
public detailsVisible$: Observable<boolean> = this.toggleVisibilitySubject.pipe(
scan((state, _) => !state, false),
startWith(false)
);
private readonly resetDetailsVisibilitySideEffect$: Observable<void> = this.resetSubject.asObservable().pipe(
withLatestFrom(this.detailsVisible$),
map(([resetTrigger, state]) => {
if (state !== resetTrigger) {
this.toggleVisibilitySubject.next();
}
})
);
constructor() {}
ngOnInit(): void {
this.resetDetailsVisibilitySideEffect$.pipe(takeUntil(this.destroySubject)).subscribe();
}
ngOnDestroy(): void {
this.destroySubject.next();
this.destroySubject.complete();
}
toggleAdditionalContentVisibility(): void {
this.toggleVisibilitySubject.next();
}
}
I want to test the detailsVisible$
-observable.
For that I created following test:
import { TestScheduler } from 'rxjs/testing';
describe('DetailComponent', () => {
const debug = true;
let scheduler: TestScheduler;
let component: DetailComponent;
beforeEach(() => {
component = new DetailComponent();
scheduler = new TestScheduler((actual, expected) => {
// asserting the two objects are equal
if (debug) {
console.log('-------------------------------');
console.log('Expected:\n' + JSON.stringify(expected, null, 2));
console.log('Actual:\n' + JSON.stringify(actual, null, 2));
}
expect(actual).toEqual(expected);
});
});
it('should finally work out', () => {
scheduler.run((helpers) => {
const { cold, hot, expectObservable, expectSubscriptions } = helpers;
const values = {
f: false,
t: true
};
const toggleVisibilityValues = {
v: void 0
};
const resetValues = {
f: false,
t: true
};
component.ngOnInit();
// marbles
// prettier-ignore
const detailsVisibleMarble = 'f-t-f-t-f-t-f';
// prettier-ignore
const toggleVisibilityMarble = '--v-v-----v--';
// prettier-ignore
const resetMarble = '------t-f---f';
// Mock observables
(component as any).toggleVisibilitySubject = cold(toggleVisibilityMarble,toggleVisibilityValues);
(component as any).resetSubject = cold(resetMarble, resetValues);
// output
expectObservable(component.detailsVisible$).toBe(detailsVisibleMarble, values);
});
});
});
I tried several things but all are resulting in the follwing output:
Expected $.length = 1 to equal 7.
Expected $[1] = undefined to equal Object({ frame: 2, notification: Notification({ kind: 'N', value: true, error: undefined, hasValue: true }) }).
Expected $[2] = undefined to equal Object({ frame: 4, notification: Notification({ kind: 'N', value: false, error: undefined, hasValue: true }) }).
Expected $[3] = undefined to equal Object({ frame: 6, notification: Notification({ kind: 'N', value: true, error: undefined, hasValue: true }) }).
Expected $[4] = undefined to equal Object({ frame: 8, notification: Notification({ kind: 'N', value: false, error: undefined, hasValue: true }) }).
Expected $[5] = undefined to equal Object({ frame: 10, notification: Notification({ kind: 'N', value: true, error: undefined, hasValue: true }) }).
Expected $[6] = undefined to equal Object({ frame: 12, notification: Notification({ kind: 'N', value: false, error: undefined, hasValue: true }) }).
<Jasmine>
So somehow the source of detailsVisible$
(toggleVisibilitySubject) is never emitting any value (I only get the startWith
-value in the result).
I do not see what I'm missing. The code itself works perfectly fine.
Thanks for any suggestions.
Edit: I also tried out to
toggle$ = this.toggleVisibilitySubject.asObservable();
public detailsVisible$ = this.toggle.pipe(...)
and in the test: component.toggle$ =cold(toggleVisibilityMarble,toggleVisibilityValues)
.