你如何模拟ActivatedRoute

时间:2017-08-28 11:26:49

标签: angular unit-testing angular-router

我正在学习Angular而且我想做测试,但是我被困了。我有一个功能:

ngOnInit(): void {
    this.route.paramMap
    .switchMap((params: ParamMap) => 
        this.SomethingService.getSomething(params.get('id')))
        .subscribe(something => {
            this.something = something;
            this.doSomethingElse();
    });
}

其中

route: ActivatedRoute

我想测试它,但我不知道如何模拟ActivatedRoute

8 个答案:

答案 0 :(得分:13)

模拟ActivatedRoute的一种简单方法是:

    TestBed.configureTestingModule({
      declarations: [YourComponenToTest],
      providers: [
        {
          provide: ActivatedRoute,
          useValue: {
            params: Observable.from([{id: 1}]),
          },
        },
      ]
    });

然后在你的测试中它将可用,你的功能应该与此一起使用(至少是ActivatedRoute部分)

如果您希望将其存储在变量中,则可以在TestBed.get(ActivatedRoute)函数中使用it获取它。

不要忘记从rxjs/Rx导入Observable,而不是从rxjs/Observable

导入

答案 1 :(得分:10)

对于任何对如何正确使用多个属性感兴趣的人,这是您定义模拟类的方式:

import { convertToParamMap } from '@angular/router';
import { Observable } from 'rxjs/Observable';

export class ActivatedRouteMock {
    public paramMap = Observable.of(convertToParamMap({ 
        testId: 'abc123',
        anotherId: 'd31e8b48-7309-4c83-9884-4142efdf7271',          
    }));
}

通过这种方式,您可以订阅paramMap并检索多个值 - 在这种情况下为testIdanotherId

答案 2 :(得分:5)

我使用paramMap而不是params面临同样的问题。这让它对我有用,至少在最简单的情况下:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { ComponentToTest } from './component-to-test.component';
import { ActivatedRoute } from '@angular/router';

TestBed.configureTestingModule({
  declarations: [ComponentToTest],
  providers: [
    { 
        provide: ActivatedRoute, 
        useValue: {
            paramMap: Observable.of({ get: (key) => 'value' })
        }
    }
  ]
});

答案 3 :(得分:5)

修改上面的Andrus的答案。 。

对于RxJS 6 +:

import { convertToParamMap } from '@angular/router';
import { of } from 'rxjs';


export class ActivatedRouteMock {
    public paramMap = of(convertToParamMap({ 
        testId: 'abc123',
        anotherId: 'd31e8b48-7309-4c83-9884-4142efdf7271',          
    }));
}

https://www.learnrxjs.io/operators/creation/of.html

答案 4 :(得分:1)

我使用 RxJS Subject 而不是 Observable 作为 paramMap。这样我就可以在测试期间触发 paramMap 发出新值。

ActivatedRoute 直接在测试床配置的提供者部分中模拟:

providers: [{ provide: ActivatedRoute, useValue: { paramMap: new Subject() } }]

然后,我从测试台获得激活的路由:

activatedRoute = TestBed.inject(ActivatedRoute);

并在测试过程中需要时触发 paramMap 发出新值:

activatedRoute.paramMap.next({ get: (key: string) => 'value1'});

答案 5 :(得分:0)

就我而言,我必须创建一个新类来处理这种类型的测试,该类将允许您处理快照,queryParams和params。

import { Params } from '@angular/router';
import { BehaviorSubject } from 'rxjs';

export class MockActivatedRoute {
  private innerTestParams?: any;
  private subject?: BehaviorSubject<any> = new BehaviorSubject(this.testParams);

  params = this.subject.asObservable();
  queryParams = this.subject.asObservable();

  constructor(params?: Params) {
    if (params) {
      this.testParams = params;
    } else {
      this.testParams = {};
    }
  }

  get testParams() {
    return this.innerTestParams;
  }

  set testParams(params: {}) {
    this.innerTestParams = params;
    this.subject.next(params);
  }

  get snapshot() {
    return { params: this.testParams, queryParams: this.testParams };
  }
}

这是测试的外观

import { MockActivatedRoute } from './mock-active-router';

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;
  let activatedRouteStub: MockActivatedRoute;

  beforeEach(async(() => {
    activatedRouteStub = new MockActivatedRoute();
    TestBed.configureTestingModule({
      declarations: [MyComponent],
      providers: [
        { provide: ActivatedRoute, useValue: activatedRouteStub }
      ]
    }).compileComponents();
  }));

  it('should change params', () => {
    expect(component.myparam).toBeUndefined();
    expect(component.paramTwo).toBeUndefined();

    activatedRouteStub.testParams = {
      myparam: 'value',
      paramTwo: 1
    };
    fixture.detectChanges();

    expect(component.myparam).toEqual('value');
    expect(component.paramTwo).toEqual(1);
  });

https://gist.github.com/dvaJi/cf552bbe6725535955f7a5eeb92d7d2e

答案 6 :(得分:0)

在最新的Angular版本中,默认情况下将启用项目的aot设置(以便更好地进行编译时类型检查)。如果您的项目就是这种情况,那么您可能至少需要存根出ActivatedRoute和ActivatedRouteSnapshot的所有属性。像这样:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Type } from '@angular/core';
import { Location } from '@angular/common';
import { MockPlatformLocation } from '@angular/common/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ActivatedRoute, ActivatedRouteSnapshot, Params, ParamMap, convertToParamMap } from '@angular/router';
import { of, BehaviorSubject } from 'rxjs';

import { HeroDetailComponent } from './hero-detail.component';
import { Hero } from '../hero';


export class MockActivatedRouteSnapshot implements ActivatedRouteSnapshot {
  private innerTestParams?: Params;

  constructor(params?: Params) {
    if (params) {
      this.testParams = params;
    } else {
      this.testParams = null;
    }
  }

  private get testParams() {
    return this.innerTestParams;
  }

  private set testParams(params: Params) {
    this.innerTestParams = params;
  }

  get paramMap() {
    return convertToParamMap(this.testParams);
  }

  get queryParamMap() {
    return this.paramMap;
  }

  get url() {
    return null;
  }

  get fragment() {
    return null;
  }

  get data() {
    return null;
  }

  get outlet() {
    return null;
  }

  get params() {
    return this.innerTestParams;
  }

  get queryParams() {
    return this.innerTestParams;
  }

  get component() {
    return null;
  }

  get routeConfig() {
    return null;
  }

  get root() {
    return null;
  }

  get parent() {
    return null;
  }

  get firstChild() {
    return null;
  }

  get children() {
    return null;
  }

  get pathFromRoot() {
    return null;
  }
}


export class MockActivatedRoute implements ActivatedRoute {
  private innerTestParams?: Params;
  private subject?: BehaviorSubject<Params> = new BehaviorSubject(this.testParams);
  private paramMapSubject?: BehaviorSubject<ParamMap> = new BehaviorSubject(convertToParamMap(this.testParams));

  constructor(params?: Params) {
    if (params) {
      this.testParams = params;
    } else {
      this.testParams = null;
    }
  }

  private get testParams() {
    return this.innerTestParams;
  }

  private set testParams(params: Params) {
    this.innerTestParams = params;
    this.subject.next(params);
    this.paramMapSubject.next(convertToParamMap(params));
  }

  get snapshot() {
    return new MockActivatedRouteSnapshot(this.testParams);
  }

  get params() {
    return this.subject.asObservable();
  }

  get queryParams() {
    return this.params;
  }

  get paramMap() {
    return this.paramMapSubject.asObservable();
  }

  get queryParamMap() {
    return this.paramMap;
  }

  get url() {
    return null;
  }

  get fragment() {
    return null;
  }

  get data() {
    return null;
  }

  get outlet() {
    return null;
  }

  get component() {
    return null;
  }

  get routeConfig() {
    return null;
  }

  get root() {
    return null;
  }

  get parent() {
    return null;
  }

  get firstChild() {
    return null;
  }

  get children() {
    return null;
  }

  get pathFromRoot() {
    return null;
  }
}


describe('HeroDetailComponent', () => {
  let component: HeroDetailComponent;
  let fixture: ComponentFixture<HeroDetailComponent>;
  let httpMock: HttpTestingController;
  let routeMock: MockActivatedRoute;
  let initialMockParams: Params;
  let locationMock: MockPlatformLocation;

  beforeEach(async(() => {
    initialMockParams = {id: 11};
    routeMock = new MockActivatedRoute(initialMockParams);
    locationMock = new MockPlatformLocation;
    TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule
      ],
      declarations: [ HeroDetailComponent ],
      providers: [
        {
          provide: ActivatedRoute, useValue: routeMock,
        },
        {
          provide: Location, useValue: locationMock,
        }
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HeroDetailComponent);
    component = fixture.componentInstance;
    httpMock = TestBed.inject<HttpTestingController>(HttpTestingController as Type<HttpTestingController>);
    fixture.detectChanges();
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('should be created', () => {
    fixture.detectChanges();

    expect(component).toBeTruthy();

    const dummyHero: Hero = { id: 11, name: 'dummyHero' };
    const req = httpMock.expectOne('api/details/11');
    req.flush(dummyHero);
  });
});

另请参阅this answer

答案 7 :(得分:0)

从一开始我就一直遇到这个问题,我发现这种方法可以按我的意愿工作。例如,无需为我做间谍。

给出:

ngOnInit() {
  this.some = this.activatedRoute.snapshot.paramMap.get('some') === 'some';
  this.else = this.activatedRoute.snapshot.paramMap.get('else');
}

然后:

describe('SomeComponent', () => {
  let component: SomeComponent;
  let fixture: ComponentFixture<SomeComponent>;

  let activatedRouteSpy;

  beforeEach(async(() => {
    activatedRouteSpy = {
      snapshot: {
        paramMap: convertToParamMap({
          some: 'some',
          else: 'else',
        })
      }
    };

    TestBed.configureTestingModule({
      declarations: [ SomeComponent ],
      providers: [
        { provide: ActivatedRoute, useValue: activatedRouteSpy },
      ]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(SomeComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should load correct data (original stuff)', fakeAsync(() => {
    component.ngOnInit();
    tick(2000);
    
    // ... do your checks ...
  }));

  it('should load correct data (different stuff)', fakeAsync(() => {
    activatedRouteSpy.snapshot.paramMap = convertToParamMap({
      some: 'some',
      else: null,
    });
    fixture.detectChanges();
    component.ngOnInit();
    tick(2000);

    // ... do your checks ...
  }));
});