How do I Mock RouterStateSnapshot for a Router Guard Jasmine test

天大地大妈咪最大 提交于 2019-12-03 05:08:28

I managed to do it slightly differently but it should work for you :

...

let mockSnapshot:any = jasmine.createSpyObj<RouterStateSnapshot>("RouterStateSnapshot", ['toString']);

@Component({
  template: '<router-outlet></router-outlet>'
})
class RoutingComponent { }

@Component({
  template: ''
})
class DummyComponent { }

describe('Testing guard', () => {
  beforeEach(() => TestBed.configureTestingModule({
    imports: [
      RouterTestingModule.withRoutes([
        {path: 'route1', component: DummyComponent},
        {path: 'route2', component: DummyComponent},
        ...
      ])
  ],
  declarations: [DummyComponent, RoutingComponent],
  providers: [
    GuardClass,
    {provide: RouterStateSnapshot, useValue: mockSnapshot}
  ]
}).compileComponents());

  it('should not allow user to overcome the guard for whatever reasons', 
    inject([GuardClass], (guard:GuardClass) => {
      let fixture = TestBed.createComponent(RoutingComponent);
      expect(guard.canActivate(new ActivatedRouteSnapshot(), mockSnapshot)).toBe(false);
  })
 ...

I needed to get the data in the route to test for user roles in my guard, so I mocked it this way:

class MockActivatedRouteSnapshot {
    private _data: any;
    get data(){
       return this._data;
    }
}

describe('Auth Guard', () => {
   let guard: AuthGuard;
   let route: ActivatedRouteSnapshot;

   beforeEach(() => {
      TestBed.configureTestingModule({
         providers: [AuthGuard, {
            provide: ActivatedRouteSnapshot,
            useClass: MockActivatedRouteSnapshot
        }]
      });
      guard = TestBed.get(AuthGuard);
  });

  it('should return false if the user is not admin', () => {
     const expected = cold('(a|)', {a: false});

     route = TestBed.get(ActivatedRouteSnapshot);
     spyOnProperty(route, 'data', 'get').and.returnValue({roles: ['admin']});

     expect(guard.canActivate(route)).toBeObservable(expected);
  });
});

Based on a previous question I had about Router I tried this...

let mockSnapshot: any;
...
mockSnapshot = jasmine.createSpyObj("RouterStateSnapshot", ['toString']);
...
TestBed.configureTestingModule({
  imports: [RouterTestingModule],
  providers:[
    {provide: RouterStateSnapshot, useValue: mockSnapshot}
  ]
}).compileComponents();
...
let test = guard.canActivate(
  new ActivatedRouteSnapshot(),
  TestBed.get(RouterStateSnapshot)
);

The problem I now have is that I need the toString here mockSnapshot = jasmine.createSpyObj("RouterStateSnapshot", ['toString']);. This is because jasmine createSpyObj requires at least one mocked method. Since I am not testing the side effects of RouterStateSnapshot, this seems like extra work for nothing.

If the purpose is just to pass the mock to the guard, it's not necessary to use createSpyObj, as suggested in other answers. The simplest solution is just to mock only required fields, which are used by canActivate method of your guard. Also, it would be better to add type safety to the solution:

const mock = <T, P extends keyof T>(obj: Pick<T, P>): T => obj as T;

it('should call foo', () => {
    const route = mock<ActivatedRouteSnapshot, 'params'>({
        params: {
            val: '1234'
        }
    });

    const state = mock<RouterStateSnapshot, "url" | "root">({
        url: "my/super/url",
        root: route // or another mock, if required
    });

    const guard = createTheGuard();
    const result = guard.canActivate(route, state);
    ...
});

If you don't use the state snapshot, just pass null instead

    const result = guard.canActivate(route, null as any);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!