I have an RxJs effect that uses a withLatestFrom operator, within my action pipe, to retrieve some state from the store. When it comes to unit testing, I’m getting the following error concerning undefined where stream was expected so clearly my observables are not set up correctly in the test. The code works as expected in System Testing.
Here’s the error:
<code>Expected $[0].notification.kind = 'E' to equal 'N'.
Expected $[0].notification.value = undefined to equal Object({ type: '[Workplan Validation] Workplan Validation Completed' }).
Expected $[0].notification.error = TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable. to equal undefined.
</code>
<code>Expected $[0].notification.kind = 'E' to equal 'N'.
Expected $[0].notification.value = undefined to equal Object({ type: '[Workplan Validation] Workplan Validation Completed' }).
Expected $[0].notification.error = TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable. to equal undefined.
</code>
Expected $[0].notification.kind = 'E' to equal 'N'.
Expected $[0].notification.value = undefined to equal Object({ type: '[Workplan Validation] Workplan Validation Completed' }).
Expected $[0].notification.error = TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable. to equal undefined.
Here is the effect code, edited:
<code>create$ = createEffect(() => this._actions$.pipe(
ofType(WorkplanValidationActions.ValidateAnomalyMediaCompletedAction),
withLatestFrom(this._storeSelector.get()),
mergeMap(([a, s]) => {
// cut out stuff just to show the unit test fails on this simple example
return of(WorkplanValidationActions.WorkplanValidationCompletedAction());
})
));
</code>
<code>create$ = createEffect(() => this._actions$.pipe(
ofType(WorkplanValidationActions.ValidateAnomalyMediaCompletedAction),
withLatestFrom(this._storeSelector.get()),
mergeMap(([a, s]) => {
// cut out stuff just to show the unit test fails on this simple example
return of(WorkplanValidationActions.WorkplanValidationCompletedAction());
})
));
</code>
create$ = createEffect(() => this._actions$.pipe(
ofType(WorkplanValidationActions.ValidateAnomalyMediaCompletedAction),
withLatestFrom(this._storeSelector.get()),
mergeMap(([a, s]) => {
// cut out stuff just to show the unit test fails on this simple example
return of(WorkplanValidationActions.WorkplanValidationCompletedAction());
})
));
Here is the unit test:
<code>it('GIVEN anomaly media to validate, WHEN all anomaly media has been validated, THEN next anomaly should be validated and event fired ', () => {
//arrange
const anomalyNumber1 = 'MTL-401-XXX';
const anomalyNumber2 = 'MTL-401-YYY';
const validationResult = {anomalyNumber: anomalyNumber2} as undefined as AnomalyMediaValidationResult;
const validationState = {
validationResults: {
workplanTaskFindingsValidationResults: [
{anomalyNumber: anomalyNumber1} as undefined as AnomalyValidationResult,
{anomalyNumber: anomalyNumber2} as undefined as AnomalyValidationResult
],
anomalyMediaValidationResults: [
{anomalyNumber: anomalyNumber1} as undefined as AnomalyMediaValidationResult,
{anomalyNumber: anomalyNumber2} as undefined as AnomalyMediaValidationResult
]
}
} as undefined as WorkplanValidationState;
const action = ValidateAnomalyMediaCompletedAction({ validationResult });
const completion = WorkplanValidationCompletedAction();
actions$ = hot('a', {a: action});
const stateResponse = cold('a|', { a: validationState });
getSelectorService.get.and.returnValue(stateResponse);
const expected = cold('b', {b: completion});
//act & assert
expect(effect.create$).toBeObservable(expected);
expect(service.validateAnomalyMedia$).not.toHaveBeenCalled();
});
</code>
<code>it('GIVEN anomaly media to validate, WHEN all anomaly media has been validated, THEN next anomaly should be validated and event fired ', () => {
//arrange
const anomalyNumber1 = 'MTL-401-XXX';
const anomalyNumber2 = 'MTL-401-YYY';
const validationResult = {anomalyNumber: anomalyNumber2} as undefined as AnomalyMediaValidationResult;
const validationState = {
validationResults: {
workplanTaskFindingsValidationResults: [
{anomalyNumber: anomalyNumber1} as undefined as AnomalyValidationResult,
{anomalyNumber: anomalyNumber2} as undefined as AnomalyValidationResult
],
anomalyMediaValidationResults: [
{anomalyNumber: anomalyNumber1} as undefined as AnomalyMediaValidationResult,
{anomalyNumber: anomalyNumber2} as undefined as AnomalyMediaValidationResult
]
}
} as undefined as WorkplanValidationState;
const action = ValidateAnomalyMediaCompletedAction({ validationResult });
const completion = WorkplanValidationCompletedAction();
actions$ = hot('a', {a: action});
const stateResponse = cold('a|', { a: validationState });
getSelectorService.get.and.returnValue(stateResponse);
const expected = cold('b', {b: completion});
//act & assert
expect(effect.create$).toBeObservable(expected);
expect(service.validateAnomalyMedia$).not.toHaveBeenCalled();
});
</code>
it('GIVEN anomaly media to validate, WHEN all anomaly media has been validated, THEN next anomaly should be validated and event fired ', () => {
//arrange
const anomalyNumber1 = 'MTL-401-XXX';
const anomalyNumber2 = 'MTL-401-YYY';
const validationResult = {anomalyNumber: anomalyNumber2} as undefined as AnomalyMediaValidationResult;
const validationState = {
validationResults: {
workplanTaskFindingsValidationResults: [
{anomalyNumber: anomalyNumber1} as undefined as AnomalyValidationResult,
{anomalyNumber: anomalyNumber2} as undefined as AnomalyValidationResult
],
anomalyMediaValidationResults: [
{anomalyNumber: anomalyNumber1} as undefined as AnomalyMediaValidationResult,
{anomalyNumber: anomalyNumber2} as undefined as AnomalyMediaValidationResult
]
}
} as undefined as WorkplanValidationState;
const action = ValidateAnomalyMediaCompletedAction({ validationResult });
const completion = WorkplanValidationCompletedAction();
actions$ = hot('a', {a: action});
const stateResponse = cold('a|', { a: validationState });
getSelectorService.get.and.returnValue(stateResponse);
const expected = cold('b', {b: completion});
//act & assert
expect(effect.create$).toBeObservable(expected);
expect(service.validateAnomalyMedia$).not.toHaveBeenCalled();
});
Things to note:
- The injected _storeSelector is a wrapper service around the store and allows me to mock its calls without using the mockStore. Therefore, I believe, I am not dependent on setting any store state as this could be any other service fetching data?
- If I remove the withLatestFrom operator and shrink the mergemap to just one param, I can remove the marble for the selector service and the unit test passes.
- If I add frames into the calls (i.e. -a) , it results in an extra error about frame 0 not equal to 20.
Any help in getting a better understanding of observables would be very much appreciated!