Angular学习笔记68:Angular项目的单元测试 -- 对路由进行测试

对路由进行测试

对于模版文件中有 的

在TestBed.configureTestingModule()的元数据的imports数据一定要加上"RouterTestingModule";
属于嵌套到组件中的其他组件,并不是单元测试的重点。

第一种处理方式-为创建和声明一些测试桩(无关紧要的组件或指令处理方式相同)

@Component({selector: 'router-outlet', template: ''})
class RouterOutletStubComponent { }

这个RouterOutletStubComponent测试桩的选择器要和其对应的真实组件一致,但其模板和类是空的,然后在TestBed.configureTestingModule的declarations数组中配置

declarations: [
    BrowseComponent,
    RouterOutletStubComponent,
  ]

第二种处理方式-把 NO_ERRORS_SCHEMA 添加到 TestBed.schemas 的元数据中。

TestBed.configureTestingModule({
  declarations: [
    AppComponent,
    RouterLinkDirectiveStub
  ],
  schemas: [ NO_ERRORS_SCHEMA ]
})

这里的NO_ERRORS_SCHEMA 会要求 Angular 编译器忽略不认识的那些元素和属性,并且不会报错,Angular只会把它们渲染成空白标签,而浏览器会忽略这些标签。

两种方法可以同时使用

对于在类文件中有通过 'navigateByUrl()‘或’navigate()’ 方法来跳转路由

在类文件中有一个跳转的路由

this.route.navigateByUrl('/Login');

测试代码:

import {async, ComponentFixture, TestBed} from '@angular/core/testing';

import {BrowseComponent} from './browse.component';
import {NgZorroAntdModule} from 'ng-zorro-antd';
import {Router} from '@angular/router';

describe('BrowseComponent', () => {
  let component: BrowseComponent;
  let router: Router;
  let fixture: ComponentFixture<BrowseComponent>;
  const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [NgZorroAntdModule],
      declarations: [BrowseComponent],
      providers: [
        {provide: Router, useValue: routerSpy}
      ]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(BrowseComponent);
    router = fixture.debugElement.injector.get(Router);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });


  it('should create and go to login', () => {
    const spy = router.navigateByUrl as jasmine.Spy;
    const navArgs = spy.calls.first().args[0];
    expect(component).toBeTruthy();
    expect(navArgs).toBe('/Login');
  });
});

  1. 在这里先声明一个 Router 的router。
let router: Router;
  1. 创建一个通过jasmine.createSpyObj() 方法创建一个routerSpy,routerSpy是关于Router的间谍(模拟对象)。
const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);
  1. 用于将 Router 注册到当前的模块中而使用的值。
{provide: Router, useValue: routerSpy}
  1. 通过TestBed 创建的实例化组件中的debugElement.injector.get(Router)方法进行初始化。
fixture = TestBed.createComponent(BrowseComponent);
    router = fixture.debugElement.injector.get(Router);
  1. 在it()函数中,通过组件调用来执行路由跳转,检测Router.navigateByUrl 曾用所期待的URL调用过。
const spy = router.navigateByUrl as jasmine.Spy;
    const navArgs = spy.calls.first().args[0];
    expect(component).toBeTruthy();
    expect(navArgs).toBe('/Login');

因为这里的跳转路由在 ngOnInit() 方法中,在expect(component).toBeTruthy()时执行了路由的跳转,所以这里只要检测 router.navigateByUrl(’/Login’) 路由是否调用过就可以了。

对于在类文件中有通过操纵注入到组件构造函数中的 ActivatedRoute 来获取路由中参数

有时会在路由中传递参数,如下:

constructor(
  private activatedRoute:  ActivatedRoute,
  private router: Router) {
}

this.activatedRoute.paramMap.subscribe((paramMap: ParamMap) => {
      this.userId = Number(paramMap.get('id'));
});

此时测试的组件需要路由的中id这个参数。

ActivatedRoute不能采用和 Router 相似的间谍来注册,这里需要另一种方式,在测试期间,paramMap 会返回一个可能会发出多个值的 Observable。此时路由器的辅助函数 convertToParamMap() 来创建 ParamMap。针对路由目标组件的其它测试需要一个 ActivatedRoute 的测试替身。因为这个可能会有多个组件需要用到在 paramMap 设置参数,所以可以将这个ActivatedRoute 的测试替身(ActivatedRouteStub)写成一个共同的文件,使其他组件在测试的过程中,比较方便。

ActivatedRouteStub 文件内容如下:

import {convertToParamMap, ParamMap, Params} from '@angular/router';
import {ReplaySubject} from 'rxjs';

export class ActivatedRouteStub {
  private subject = new ReplaySubject<ParamMap>();

  constructor(initialParams?: Params) {
    this.setParamMap(initialParams);
  }

  /** The mock paramMap observable */
  readonly paramMap = this.subject.asObservable();

  /** Set the paramMap observables's next value */
  setParamMap(params?: Params) {
    this.subject.next(convertToParamMap(params));
  }
}
  • 使用 ActivatedRouteStub 进行测试
    在路由中增加参数,是在测试组件没有被创建之前,最好在当前测试的组件没有被TestBed动态创建之前设置。
let activatedRoute: ActivatedRouteStub;
let router: Router;
describe('loadRouter', () => {
  beforeEach(() => {
    activatedRoute = new ActivatedRouteStub();
  });
  describe('UserComponent', () => {
    let component: UserComponent;
    let fixture: ComponentFixture<UserComponent>;
  });
});

然后在testBed中提供 ActivatedRoute 并且使用的值为:ActivatedRoute的替身activatedRoute;

TestBed.configureTestingModule({
        imports: [
          NgZorroAntdModule,
          SecondaryTitleModule,
          BreadcrumbModule,
          FormsModule,
          ReactiveFormsModule,
          HttpClientTestingModule,
          TranslateModule.forRoot(),
          ButtonModule,
          RouterTestingModule,
        ],
        declarations: [UserComponent],
        providers: [
          TranslateService,
          HttpService,
          Location,
          {provide: Router, useValue: routerSpy},
          {provide: ActivatedRoute, useValue: activatedRoute},
        ]
      })

接下来就是设置路由中的参数了

beforeEach(() => {
      activatedRoute.setParamMap({id: 1});
      fixture = TestBed.createComponent(UserComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
});

设置好了以后,对相应的代码段进行测试

it('should get activatedRoute params', () => {
      router = fixture.debugElement.injector.get(Router);
      component.ngOnInit();
      expect(component.userId).toBe(1);
});

对于带有 RouterLink 的组件

有时会在模版中使用 RouterLink 指令来跳转路由,

<a href="javascript:void(0)" [routerLink]="'/xxx/www/'">{{userName}}</a>

在实际的 RouterLinkDirective 太复杂了,而且 RouterLinkDirective 与 RouterModule 中的其它组件和指令有着非常复杂的联系,所以化繁为简,在这里使用 RouterLinkDirectiveStub 代替换了真实的指令。

@Directive({
  selector: '[routerLink]'
})
export class RouterLinkDirectiveStub {
  @Input('routerLink') linkParams: any;
  navigatedTo: any = null;

  @HostListener('click')
  onClick() {
    this.navigatedTo = this.linkParams;
  }
}

import {NgModule} from '@angular/core';

@NgModule({
  declarations: [
    RouterLinkDirectiveStub
  ]
})
export class RouterStubsModule {
}

这里的 URL 被绑定到了 [routerLink] 属性上,它的值流入了该指令的 linkParams 属性,并将宿主元素的点击事件关联到了这里的onClick()方法上。

  • 将RouterLinkDirectiveStub 引入到 TestBed.configureTestingModule 的 declarations 的数组中
beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        NgZorroAntdModule,
        TranslateModule.forRoot(),
      ],
      declarations: [HeaderComponent, RouterLinkDirectiveStub],
      providers: [
        TranslateService,
        {provide: Router, useValue: routerSpy}]
    })
      .compileComponents();
  }));
  • 在 describe() 中使用 RouterLinkDirectiveStub
describe('HeaderComponent', () => {
  let routerLinks: RouterLinkDirectiveStub[];
  });
  • 使用By.directive 获取导航链接的引用
beforeEach(() => {
  fixture = TestBed.createComponent(HeaderComponent);
  component = fixture.componentInstance;
  router = fixture.debugElement.injector.get(Router);
  translate = fixture.debugElement.injector.get(TranslateService);
  nzI18nService = fixture.debugElement.injector.get(NzI18nService);
  fixture.detectChanges();
  linkDes = fixture.debugElement.queryAll(By.directive(RouterLinkDirectiveStub));
  routerLinks = linkDes.map(de => de.injector.get(RouterLinkDirectiveStub));
});
  1. 使用 By.directive 来定位一个带有附属指令的指令的链接元素。
  2. queryAll(By.directive(RouterLinkDirectiveStub))查询返回包含了匹配元素的 DebugElement 包装器。
  3. 每个 DebugElement 都会导出该元素中的一个依赖注入器,其中带有指定的指令实例。
  • 验证是否导航正确
it('can get RouterLinks from template', () => {
    expect(routerLinks.length).toBe(3, 'should have 3 routerLinks');
    expect(routerLinks[0].linkParams).toBe('/Devops/MyWork/Account/AccountView');
    expect(routerLinks[1].linkParams).toBe('MyWork');
    expect(routerLinks[2].linkParams).toBe('MyProject');
  });
  
 it('can click MyProject link in template', () => {
    const myProjectLinkDe = linkDes[2];
    const myProjectLink = routerLinks[2];
    expect(myProjectLink.navigatedTo).toBeNull('should not have navigated yet');
    myProjectLinkDe.triggerEventHandler('click', null);
    fixture.detectChanges();
    expect(myProjectLink.navigatedTo).toBe('MyProject');
  });

第一个测试用例是对当前组件模版中的所有带有 routerLink 指令的元素进行汇总查找,第二个是对其中一个进行单独模拟点击的测试。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值