我是该框架中的 Angular 和单元测试的新手。我正在尝试测试我在下面的身份验证保护:
import { AuthenticationService } from '../auth/authentication.service';
import { map, take } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class AuthenticationGuard implements CanActivate, CanActivateChild {
constructor(private authService: AuthenticationService, private router: Router){}
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
return this.canActivate(childRoute,state,)
}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.canProceed()
}
private canProceed(): Observable<boolean | UrlTree>{
return this.authService.user.pipe(
take(1),
map( user => {
const isAuth = !!user;
if(isAuth) return true;
return this.router.createUrlTree(['/login'])
})
);
}
}
我很难理解如何测试这个。我做了研究,大多数人模拟路由器对他们的守卫进行测试,但是当我尝试这样做时,我不断收到错误消息。我还没有真正找到任何测试可观察守卫的东西。她是我写的测试。
describe('AuthenticationGuard', () => {
let injector: TestBed;
let authService: AuthenticationService;
let guard: AuthenticationGuard;
let routeMock: any = { snapshot: {}};
let routeStateMock: any = { snapshot: {}, url: '/login'};
let routerMock = {navigate: jasmine.createSpy('navigate')}
beforeEach(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes([]),HttpClientTestingModule],
providers: [{ provide: Router, useValue: routerMock },],
});
injector = getTestBed()
authService = injector.inject(AuthenticationService)
guard = TestBed.inject(AuthenticationGuard);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
it('should allow the authenticated user to access app', () => {
expect(true).toEqual(true)
})
it('should redirect an unauthenticated user to the login route', () => {
authService.user = new BehaviorSubject<User | null>(null);
expect(guard.canActivate(routeMock, routeStateMock)).toEqual(of(false))
expect(routerMock.navigate).toHaveBeenCalledWith(['/login']);
})
});
在我的身份验证服务中,我有一个包含用户信息的行为主题,如果用户注销,则更新为 null,如果用户信息已更新,则更新用户信息。 我知道错误是预期的匿名主题。我正在努力了解她发生了什么,任何帮助都会很棒。
答案 0 :(得分:0)
您可以测试 canActivate
或 canActivateChild
的返回值或/并测试对 createUrlTree
的调用。您不必期待 router.navigate
的电话。
这是我的尝试。我created a stackblitz通过了测试。
您可以从 canActivate
或 canActivateChild
订阅重新调整的 Observable 并将您的期望插入其中,不要忘记调用 doneFn 通知 jasmine 测试已完成。
身份验证服务:
import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";
import { User } from "./user";
@Injectable({
providedIn: "root"
})
export class AuthenticationService {
/**
* The user subject
*/
private readonly userSubject: BehaviorSubject<User> = new BehaviorSubject(
null
);
// it's good practice to disallow public access to your subjects.
// so that's why we create this public observable to which components can subscibe.
public $user = this.userSubject.asObservable();
constructor() {}
}
AuthGuard:
import { map, take } from 'rxjs/operators';
import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { AuthenticationService } from './auth.service.ts';
@Injectable({
providedIn: 'root'
})
export class AuthenticationGuard implements CanActivate, CanActivateChild {
constructor(private authService: AuthenticationService, private router: Router){}
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
return this.canActivate(childRoute,state,)
}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.canProceed()
}
private canProceed(): Observable<boolean | UrlTree>{
return this.authService.$user.pipe(
take(1),
map( user => {
const isAuth = !!user;
if(isAuth) return true;
return this.router.createUrlTree(['/login'])
})
);
}
}
AuthGuard 测试:
import { getTestBed, TestBed } from "@angular/core/testing";
import { Router, UrlTree } from "@angular/router";
import { RouterTestingModule } from "@angular/router/testing";
import { BehaviorSubject, of } from "rxjs";
import { AuthenticationGuard } from "./auth.guard";
import { AuthenticationService } from "./auth.service";
import { User } from "./user";
describe("AuthenticationGuard", () => {
let injector: TestBed;
let authService: AuthenticationService;
let guard: AuthenticationGuard;
let routeMock: any = { snapshot: {} };
let routeStateMock: any = { snapshot: {}, url: "/login" };
let router: Router;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes([])]
});
injector = getTestBed();
authService = injector.inject(AuthenticationService);
guard = TestBed.inject(AuthenticationGuard);
router = TestBed.inject(Router);
});
it("should be created", () => {
expect(guard).toBeTruthy();
});
it("should allow the authenticated user to access app", () => {
expect(true).toEqual(true);
});
it("should redirect an unauthenticated user to the login route", done => {
const createUrlTreeSpy = spyOn(router, "createUrlTree").and.callThrough();
authService.$user = new BehaviorSubject<User | null>(null);
guard.canActivate(routeMock, routeStateMock).subscribe({
next: result => {
console.log("urltree:", result);
expect(createUrlTreeSpy).toHaveBeenCalledWith(["/login"]);
done();
},
error: err => {
console.log("i don't end up here");
done();
}
});
});
it("should return true and not redirect", done => {
const createUrlTreeSpy = spyOn(router, "createUrlTree").and.callThrough();
authService.$user = new BehaviorSubject<User | null>(new User());
guard.canActivate(routeMock, routeStateMock).subscribe({
next: result => {
console.log("result:", result);
expect(result).toBeTruthy();
expect(createUrlTreeSpy).not.toHaveBeenCalledWith(["/login"]);
done();
},
error: err => {
console.log("i don't end up here");
done();
}
});
});
});