如何在angular2中模拟一个activatedRoute父路由以进行测试?

S.G*_*eau 37 unit-testing mocking jasmine typescript angular

假设我有这个

export class QuestionnaireQuestionsComponent {

    questions: Question[] = [];
    private loading:boolean = true;


    constructor(
        private route: ActivatedRoute,
        public questionnaireService:QuestionnaireService) {}

    ngOnInit(){
        this.route.parent.params.subscribe((params:any)=>{
            this.questionnaireService.getQuestionsForQuestionnaire(params.id).subscribe((questions)=>{
                this.questions = questions;
                this.loading = false;
            });
        });
    }


}
Run Code Online (Sandbox Code Playgroud)

我的组件实际上工作得很好.问题是我想对它进行单元测试,但我无法弄清楚如何模拟this.route.parent对象.这是我的测试失败了

beforeEach(()=>{
    route = new ActivatedRoute();
    route.parent.params = Observable.of({id:"testId"});

    questionnaireService = jasmine.createSpyObj('QuestionnaireService', ['getQuestionsForQuestionnaire']);
    questionnaireService.getQuestionsForQuestionnaire.and.callFake(() => Observable.of(undefined));
    component = new QuestionnaireQuestionsComponent(route, questionnaireService);
});


describe("on init", ()=>{
    it("must call the service get questions for questionnaire",()=>{
        component.ngOnInit();
        expect(questionnaireService.getQuestionsForQuestionnaire).toHaveBeenCalled();
    });  
});
Run Code Online (Sandbox Code Playgroud)

测试因此错误而失败

TypeError: undefined is not an object (evaluating 'this._routerState.parent') 
Run Code Online (Sandbox Code Playgroud)

小智 45

使用TestBed

 beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [YourComponent],
      imports: [],
      providers: [
        {
          provide: ActivatedRoute, useValue: {
            params: Observable.of({ id: 'test' })
          }
        }
      ]
    })
      .compileComponents();
  }));
Run Code Online (Sandbox Code Playgroud)

  • 如何更改 it(...) 中 id 的值? (3认同)

S.G*_*eau 22

AcitvatedRoute是一个根据angular2 docs的接口,所以我所做的是实现一个MockActivatedRoute

import {Observable} from 'rxjs';
import {Type} from '@angular/core';
import {ActivatedRoute,Route,ActivatedRouteSnapshot,UrlSegment,Params,Data } from '@angular/router';

export class MockActivatedRoute implements ActivatedRoute{
    snapshot : ActivatedRouteSnapshot;
    url : Observable<UrlSegment[]>;
    params : Observable<Params>;
    queryParams : Observable<Params>;
    fragment : Observable<string>;
    data : Observable<Data>;
    outlet : string;
    component : Type<any>|string;
    routeConfig : Route;
    root : ActivatedRoute;
    parent : ActivatedRoute;
    firstChild : ActivatedRoute;
    children : ActivatedRoute[];
    pathFromRoot : ActivatedRoute[];
    toString() : string{
        return "";
    };
}
Run Code Online (Sandbox Code Playgroud)

ActivatedRoute在我的测试中替换MockActivatedRoute这样的

beforeEach(()=>{
    route = new MockActivatedRoute();
    route.parent = new MockActivatedRoute();
    route.parent.params = Observable.of({id:"testId"});

    questionnaireService = jasmine.createSpyObj('QuestionnaireService', ['getQuestionsForQuestionnaire']);
    questionnaireService.getQuestionsForQuestionnaire.and.callFake(() => Observable.of(undefined));
    component = new QuestionnaireQuestionsComponent(route, questionnaireService);
});
Run Code Online (Sandbox Code Playgroud)

  • 我无法分配“父”,因为它是常量或只读属性。还有缺少道具和接口:paramMap 和 queryParamMap。应该为这些设置什么,这会在 Anuglar 4 中起作用吗? (3认同)

Gar*_*ing 10

对于这个问题的新手,angular.io文档涵盖了这个场景.看到这里

根据上述文件:

创建ActivatedRouteStub要用作测试双精度的类ActivatedRoute

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

/**
 * An ActivateRoute test double with a `paramMap` observable.
 * Use the `setParamMap()` method to add the next `paramMap` value.
 */
export class ActivatedRouteStub {
  // Use a ReplaySubject to share previous values with subscribers
  // and pump new values into the `paramMap` observable
  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));
  };
}
Run Code Online (Sandbox Code Playgroud)

然后在测试类中使用stub,如下所示:

activatedRoute.setParamMap({ id: '1234' });
Run Code Online (Sandbox Code Playgroud)


小智 5

要在每个“it”块中自定义模拟的 ActivatedRoute 数据,请结合上面 Kevin Black 的建议

beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [YourComponent],
      imports: [],
      providers: [
        {
          provide: ActivatedRoute, useValue: {
            queryParams: of({ id: 'test' })
          }
        }
      ]
    })
      .compileComponents();
  }));
Run Code Online (Sandbox Code Playgroud)

在使用fixture.detectChanges() 实例化组件之前,it('') 块中的以下代码

it('test', fakeAsync(() => {
  let activatedRoute: ActivatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
  activatedRoute.queryParams = of({ firstName: 'john', lastName: 'smith' });

  fixture.detectChanges(); // trigger ngOninit()
  tick();
}));
Run Code Online (Sandbox Code Playgroud)


小智 5

对于从ActivatedRouteSnapshot读取查询参数的场景,例如:

this.someProperty = this.route.snapshot.data.query['paramKey'];
Run Code Online (Sandbox Code Playgroud)

下面的代码将有助于测试将查询参数数据提供给路由

{ 
  provide: ActivatedRoute, 
  useValue: {
    snapshot: {
      data: {
        query: {
          paramKey: 'paramValue'
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)