简体   繁体   中英

Mocking RouterStateSnapshot in Jasmine testing

Although I have been writing Angular 2 for a while now, I am only just writing my first Jasmine tests and have run into a little difficulty. I am trying to test that the CanActivate method of service implementing CanActivate is behaving itself, and is returning true or false as expected.

My method looks like this:

canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> {
    return this.store$
        .map( ( store: StoreState ) => store.currentUser )
        .first()
        .map( ( user ) => {
            if ( user.isAuthenticated ) {
                return true;
            }

            // TODO: This needs refactoring. Need to provide RouterStateSnapshot in test,
            // rather than ignoring it!
            this.redirectUrl = state ? state.url : '';
            this.injector.get( Router ).navigate( ['/login'] );
            return false;
        } );
}

An extract of my test looks like this:

service = TestBed.get( AuthGuardService );

it( 'should prevent navigation', () => {
    service.canActivate(null, null).subscribe((res) => expect( res ).toBeTruthy() );
} );

How do I mock/stub/whatever the second parameter of my call to service.canActivate , rather than simply passing in null?

describe('AuthGuard', () => {
  let mockSnapshot: RouterStateSnapshot;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        // so we can get the Router injected
        RouterTestingModule,
        // other imports as needed
      ],
      // usual config here
    });

    // create a jasmine spy object, of the required type
    // toString is because we have to mock at least one method
    mockSnapshot = createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
  });

  it('should prevent non-authenticated access',
    async(inject([AuthGuard, AuthService, Router], (guard: AuthGuard, auth: AuthService, router: Router) => {
      // ensure we're logged out
      auth.logout();

      // set the url on our mock snapshot
      mockSnapshot.url = '/protected';

      // so we can spy on what's been called on the router object navigate method
      spyOn(router, 'navigate');

      expect(guard.canActivate(null, mockSnapshot)).toBeFalsy();

      // check that our guard re-directed the user to another url
      expect(router.navigate).toHaveBeenCalled();
    })));
  });
})

Here is my solution which I used for unit testing of Custom Router State Serializer

custom-serializer.ts

import { RouterStateSerializer } from '@ngrx/router-store';
import { RouterStateSnapshot, Params } from '@angular/router';

/**
 * The RouterStateSerializer takes the current RouterStateSnapshot
 * and returns any pertinent information needed. The snapshot contains
 * all information about the state of the router at the given point in time.
 * The entire snapshot is complex and not always needed. In this case, you only
 * need the URL and query parameters from the snapshot in the store. Other items could be
 * returned such as route parameters and static route data.
 */

export interface RouterStateUrl {
  url: string;
  params: Params;
  queryParams: Params;
}

export class CustomRouterStateSerializer
  implements RouterStateSerializer<RouterStateUrl> {
  serialize(routerState: RouterStateSnapshot): RouterStateUrl {
    let route = routerState.root;

    while (route.firstChild) {
      route = route.firstChild;
    }

    const { url, root: { queryParams } } = routerState;
    const { params } = route;

    // Only return an object including the URL, params and query params
    // instead of the entire snapshot
    return { url, params, queryParams };
  }
}

custom-serializer.spec.ts

import { CustomRouterStateSerializer } from './utils';
import { RouterStateSnapshot } from '@angular/router';


describe('Utils CustomRouterStateSerializer', () => {
    let mockSnapshot: RouterStateSnapshot;
    let serializer: CustomRouterStateSerializer;
    let mockSnapshotProxy;
    beforeEach(() => {
        mockSnapshot = jasmine.createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
        serializer = new CustomRouterStateSerializer();
    });

    it('should serialize RouterStateSnapshot to subset of params', () => {
        mockSnapshotProxy = new Proxy(mockSnapshot, {
            get(target, prop) {
                if (prop === 'root') {
                    return {
                        params: {
                            id: 100
                        },
                        queryParams: {
                            name: 'John'
                        }
                    };
                } else if (prop === 'url') {
                    return '/orders';
                }
            },
        });
        const result = serializer.serialize(mockSnapshotProxy);
        expect(result.url).toBe('/orders');
        expect(result.params.id).toBe(100);
        expect(result.queryParams.name).toBe('John');
    });

});

I used jasmine.createSpyObj to create object with proper type and Proxy to pass in required properties

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM