5
votes

I'm working on two nearly identical test cases where I should assert expect that two NgRx effects return a stream of booleans. Everything works as expected in the first test case, and although doing the same for the second test case I can't make it to work as expected. Whatever I do, the received value is always an empty array:

expect(received).toEqual(expected) // deep equality
    - Expected
    + Received

    - Array [
    -   Object {
    -     "frame": 10,
    -     "notification": Notification {
    -       "error": undefined,
    -       "hasValue": true,
    -       "kind": "N",
    -       "value": true,
    -     },
    -   },
    - ]
    + Array []

Working test

logger.effects.spec.ts:

describe('LoggerEffects', () => {
    let actions$: Observable<ApiFailure>;
    let effects: LoggerEffects;
    const serviceMock = <AppLoggerServiceI>{ failure: ({ body, title }) => of(true) };
    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [StoreModule.forRoot({})],
            providers: [
                LoggerEffects,
                provideMockActions(() => actions$),
                {
                    provide: 'appLoggerService',
                    useValue: serviceMock
                }
            ]
        });
        effects = TestBed.get(LoggerEffects);
    });
    const failure = new HttpErrorResponse({ error: 'HttpErrorResponse' });
    describe('logApiFailure$', () => {
        it('should return a stream of boolean if the dispatched action is ApiActions.searchFailure', () => {
            const action = ApiActions.searchFailure({ failure });
            actions$ = hot('-a--', { a: action });
            const expected = cold('-b--', { b: true });
            expect(effects.logApiFailure$).toBeObservable(expected);
        });
    });
});

logger.effects.ts:

logApiFailure$ = createEffect(
        () =>
            this.actions$.pipe(
                ofType(ApiActions.searchFailure),
                exhaustMap(({ failure: { body, title } }) =>
                    this.appLoggerService.failure({
                        body,
                        title
                    })
                )
            ),
        { dispatch: false }
    );

Failed test

router.effects.spec.ts:

describe('RouterEffects', () => {
    let actions$: Observable<boolean>;
    let effects: RouterEffects;

    const routerMock = <Partial<Router>>{ navigate: (commands: any[]) => Promise.resolve(true) };

    let router: Router;
    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [StoreModule.forRoot({})],
            providers: [
                RouterEffects,
                provideMockActions(() => actions$),
                {
                    provide: Router,
                    useValue: routerMock
                }
            ]
        });
        effects = TestBed.get(RouterEffects);
    });
    describe('navigateTo$', () => {
        it('should return a stream of boolean if the dispatched action is RouterActions.navigateTo', () => {
            const action = RouterActions.navigateTo();
            actions$ = hot('-a--', { a: action });
            const expected = cold('-b--', { b: true });
            console.log('toto', effects.navigateTo$);
            expect(effects.navigateTo$).toBeObservable(expected);
        });
    });
});

router.effects.ts:

navigateTo$ = createEffect(
    () =>
        this.actions$.pipe(
            ofType(RouterActions.navigateTo),
            exhaustMap(() => this.router.navigate(['my-route', {}]))
        ),
    { dispatch: false }
);

At this point, I'm assuming there's something wrong with the Hot Observable in router.effects.spec.ts which prevents the effect to dispatch the navigateTo action. Do you have any thoughts about it?

The project's dependencies

{
    "dependencies": {
        "@angular/animations": "^8.0.0",
        "@angular/cdk": "^8.0.0",
        "@angular/common": "^8.0.0",
        "@angular/compiler": "^8.0.0",
        "@angular/core": "^8.0.0",
        "@angular/forms": "^8.0.0",
        "@angular/material": "^8.0.0",
        "@angular/material-moment-adapter": "^8.0.0",
        "@angular/platform-browser": "^8.0.0",
        "@angular/platform-browser-dynamic": "^8.0.0",
        "@angular/router": "^8.0.0",
        "@ngrx/effects": "^8.0.0",
        "@ngrx/router-store": "^8.0.0",
        "@ngrx/store": "^8.0.0",
        "core-js": "^2.5.4",
        "hammerjs": "^2.0.8",
        "moment": "^2.24.0",
        "rxjs": "^6.5.0",
        "tslib": "^1.9.0",
        "zone.js": "^0.9.1"
    },
    "devDependencies": {
        "jest-preset-angular": "7.0.0",
        "@angular-devkit/build-angular": "^0.800.0",
        "@angular-devkit/build-ng-packagr": "~0.800.0",
        "@angular-devkit/core": "^8.0.2",
        "@angular-devkit/schematics": "^8.0.2",
        "@angular/cli": "8.0.0",
        "@angular/compiler-cli": "^8.0.0",
        "@angular/language-service": "^8.0.0",
        "@ngrx/schematics": "^8.0.0",
        "@ngrx/store-devtools": "^8.0.0",
        "@nrwl/cypress": "8.1.0",
        "@nrwl/jest": "8.1.0",
        "@nrwl/workspace": "8.1.0",
        "@nrwl/schematics": "8.1.0",
        "@types/jest": "24.0.9",
        "@types/node": "^12.0.7",
        "codelyzer": "~5.0.1",
        "cypress": "~3.3.1",
        "dotenv": "6.2.0",
        "jest": "24.1.0",
        "ng-packagr": "^5.1.0",
        "prettier": "1.16.4",
        "ts-jest": "24.0.0",
        "ts-node": "^8.2.0",
        "tsickle": "^0.35.0",
        "tslib": "^1.9.0",
        "tslint": "~5.11.0",
        "typescript": "~3.4.5"
    }
}
1

1 Answers

0
votes

I just spent some time tracking down a similar issue (empty expected array) and the problem turned out to be related to asynchronous code that was not managed by the TestScheduler. In my case I was using from([Promise]). To fix the issue I mocked the function returning a promise to return an observable of().

This issue helped me a lot and explains the problem clearly: https://github.com/cartant/rxjs-marbles/issues/11

In your case I believe the problem is being caused by this.router.navigate(['my-route', {}]) which returns a promise, not an observable.

Try mocking router.navigate to return of(true).