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

时间:2016-11-24 15:06:46

标签: unit-testing angular typescript mocking jasmine

我们说我有这个

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;
            });
        });
    }


}

我的组件实际上工作得很好。问题是我想对它进行单元测试,但我无法弄清楚如何模拟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();
    });  
});

测试因此错误而失败

TypeError: undefined is not an object (evaluating 'this._routerState.parent') 

8 个答案:

答案 0 :(得分:37)

使用TestBed



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




答案 1 :(得分:17)

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 "";
    };
}

并且只需替换我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);
});

答案 2 :(得分:8)

对于此问题的新用户,angular.io文档涵盖了此方案。见here

根据上述文件:

创建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));
  };
}

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

activatedRoute.setParamMap({ id: '1234' });

答案 3 :(得分:3)

要在每个“ it”块中自定义模拟的ActivatedRoute数据,请结合凯文·布莱克(Kevin Black)的建议

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

和it('')块中的以下代码,然后使用Fixture.detectChanges()实例化组件

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

  fixture.detectChanges(); // trigger ngOninit()
  tick();
}));

答案 4 :(得分:2)

对于要从 ActivatedRouteSnapshot 读取查询参数的情况,如:

this.someProperty = this.route.snapshot.data.query['paramKey'];

下面的代码将有助于将查询参数数据输入到路由中的测试

{ 
  provide: ActivatedRoute, 
  useValue: {
    snapshot: {
      data: {
        query: {
          paramKey: 'paramValue'
        }
      }
    }
  }
}

答案 5 :(得分:2)

Angular 9 版本

  1. 创建您的模拟:

     const mockActivatedRoute = { 
       parent: { 
         paramMap: of(convertToParamMap(
             { organization: 'fakeOrganization' }
         ))}
     };
    
  2. 提供它作为激活的路由

      providers: [{ provide: ActivatedRoute, useValue: mockActivatedRoute }]
    
  3. 测试一下

     describe('ngOnInit', () => {
         it('should get the route parameter', fakeAsync(() => {
           component.ngOnInit();
    
           expect(component.organization).toBe('fakeOrganization');
         }));
       });'
    
  4. 正在测试的代码

       ngOnInit(): void {
         this.route.parent.paramMap.subscribe(params => {
           this.organization = params.get('organization');
         });
       }
    

答案 6 :(得分:1)

您还可以简单地播放{params: {searchTerm: 'this is not me'}} as any) as ActivatedRouteSnapshot

删除代码

  (service.resolve(({params: {id: 'this is id'}} as any) as ActivatedRouteSnapshot,
    {} as RouterStateSnapshot)as Observable<xyzResolveData>)
    .subscribe((data) => {
      expect((data as xyzResolveData).results).toEqual(xyzData.results);
    });

答案 7 :(得分:0)

基于@S.Galarneau 接受的答案,这里是更新的 ActivatedRoute 接口(angular 4+),带有访问修饰符,仅使用少数类型,以及使用 providers 数组并与 TestBed 集成:

这非常适合创建模拟界面并使用 providers 数组来使用您的 ActivatedRoute 版本:

running = True
while running:
    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            running = False

        elif e.type == pygame.KEYDOWN:
            
            # print key
            print(e.key)

            # print unicode representation
            print(e.unicode)

            # print user firendly name
            print(pygame.key.name(e.key))

和TestBed和Providers数组一起使用,只处理fragment属性,像这样:

export class MockActivatedRoute implements ActivatedRoute{
  public snapshot;
  public url;
  public params;
  public queryParams: Observable<Params>;
  public fragment: Observable<string>;
  public data;
  public outlet;
  public component;
  public paramMap;
  public queryParamMap;
  public routeConfig;
  public root;
  public parent;
  public firstChild;
  public children;
  public pathFromRoot;
  public toString(): string {
    return '';
  };
}