你如何模拟ActivatedRoute

Dar*_*ała 13 unit-testing angular angular-router

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

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

哪里

route: ActivatedRoute
Run Code Online (Sandbox Code Playgroud)

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

小智 18

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

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

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

如果要将其存储在变量中,可以TestBed.get(ActivatedRoute)it函数中使用它.

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

  • 对于 `paramMap`: `useValue: { paramMap: of(convertToParamMap({id: 1})) },` (3认同)
  • 这不起作用,因为OP使用的是`paramMap`而不是`params`。 (2认同)
  • 而不是从rxjs / Rx导入observable,我使用了`of([[{id:1}]))。(`从'rxjs'导入{of};`) (2认同)

And*_*ius 13

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

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',          
    }));
}
Run Code Online (Sandbox Code Playgroud)

这样您就可以订阅paramMap并检索多个值 - 在这种情况下testIdanotherId.

  • “ActivatedRouteMock”类型的参数不可分配给“ActivatedRoute”类型的参数。“ActivatedRouteMock”类型缺少“ActivatedRoute”类型中的以下属性:url、params、queryParams、fragment 等 11 个 (2认同)

小智 9

我使用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' })
        }
    }
  ]
});
Run Code Online (Sandbox Code Playgroud)


sch*_*uno 7

在上面修改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',          
    }));
}
Run Code Online (Sandbox Code Playgroud)

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

  • “ActivatedRouteMock”类型的参数不可分配给“ActivatedRoute”类型的参数。“ActivatedRouteMock”类型缺少“ActivatedRoute”类型中的以下属性:url、params、queryParams、fragment 等 11 个 (2认同)

小智 5

就我而言,我必须创建一个新类来处理此类测试,该类将允许您处理快照、查询参数和参数。

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 };
  }
}
Run Code Online (Sandbox Code Playgroud)

这就是测试的样子

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);
  });
Run Code Online (Sandbox Code Playgroud)

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


Ste*_*ggy 5

在最近的 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);
  });
});
Run Code Online (Sandbox Code Playgroud)

另请参阅此答案


小智 5

我一直有这个问题,我发现这种方式可以按我的意愿工作。例如,无需为我监视 get。

鉴于:

ngOnInit() {
  this.some = this.activatedRoute.snapshot.paramMap.get('some') === 'some';
  this.else = this.activatedRoute.snapshot.paramMap.get('else');
}
Run Code Online (Sandbox Code Playgroud)

然后:

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 ...
  }));
});
Run Code Online (Sandbox Code Playgroud)