【问题标题】:How do you mock ActivatedRoute你如何模拟ActivatedRoute
【发布时间】:2017-08-28 11:26:48
【问题描述】:

我正在学习 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

【问题讨论】:

    标签: angular unit-testing angular-router


    【解决方案1】:

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

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

    然后在您的测试中它将可用并且您的函数应该可以使用它(至少是 ActivatedRoute 部分)

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

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

    【讨论】:

    • 这不起作用,因为 OP 使用的是 paramMap 而不是 params
    • 我没有从 rxjs/Rx 导入 o​​bservable,而是使用了of([{id: 1}])。 (import { of } from 'rxjs';)
    • 也许有人会觉得这很有用。我需要在测试之间轻松设置activatedRoute paramsMap 和URL。 gist.github.com/rossholdway/89a50d466d55bbed8d402c2d81f44741 通过调用 route.set('categories/:slug', {slug: 'featured'}); 提供了一种简单的方法来执行此操作。参数是可选的。
    • 对于paramMapuseValue: { paramMap: of(convertToParamMap({id: 1})) },
    • 补充罗斯上面所说的。您可以获得激活的路由模拟/存根并添加 params 对象和重播主题来解决您的问题(只需按照 paramMap 的模式。或者,从这里下载完成的一个 github.com/adrobson/activatedroutestub。然后就在您创建组件之前你的测试,调用 mockActivatedRoute.set 并传递你的参数参数。
    【解决方案2】:

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

    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

    【讨论】:

    • “ActivatedRouteMock”类型的参数不可分配给“ActivatedRoute”类型的参数。 “ActivatedRouteMock”类型缺少“ActivatedRoute”类型中的以下属性:url、params、queryParams、fragment 和另外 11 个
    • @lohiarahul 一定有某种类型的强制检查。
    • 如何在具体测试中设置所需的 paramID?比如让我们说一个测试需要testId,而另一个需要另一个Id? @AndriusNaruševičius
    【解决方案3】:

    我在使用 paramMap 而不是 params 时遇到了同样的问题。这让它对我有用,至少对于最简单的情况:

    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    import { Observable } from 'rxjs';
    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' })
            }
        }
      ]
    });
    

    【讨论】:

      【解决方案4】:

      修改 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

      【讨论】:

      • 为什么Observable import 离开了?
      • 我的错误,删除它。 . .
      • “ActivatedRouteMock”类型的参数不可分配给“ActivatedRoute”类型的参数。 “ActivatedRouteMock”类型缺少“ActivatedRoute”类型中的以下属性:url、params、queryParams、fragment 和另外 11 个
      【解决方案5】:

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

      给定:

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

      【讨论】:

        【解决方案6】:

        在我的例子中,我必须创建一个新类来处理这种类型的测试,这个类将允许您处理快照、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

        【讨论】:

        • 这使我能够测试几个不同的参数。谢谢
        【解决方案7】:

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

        ActivatedRoute 直接在 Testbed 配置的 providers-section 中模拟:

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

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

        activatedRoute = TestBed.inject(ActivatedRoute);
        

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

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

        【讨论】:

        • 谢谢,这对我有用...非常有用,因为您可以准确发送您需要的内容。
        【解决方案8】:

        在最近的 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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-10-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-03-13
          • 1970-01-01
          相关资源
          最近更新 更多