如何对依赖于ActivatedRoute参数的组件进行单元测试?

问题描述 投票:0回答:13

我正在对用于编辑对象的组件进行单元测试。该对象具有唯一的

id
,用于从服务中托管的对象数组中获取特定对象。具体的
id
是通过路由传递的参数获取的,特别是通过
ActivatedRoute
类。

构造函数如下:

constructor(private _router:Router, private _curRoute:ActivatedRoute, private _session:Session) {}
    
ngOnInit() {
  this._curRoute.params.subscribe(params => {
    this.userId = params['id'];
    this.userObj = this._session.allUsers.filter(user => user.id.toString() === this.userId.toString())[0];

我想对此组件运行基本的单元测试。但是,我不确定如何注入

id
参数,并且组件需要这个参数。

顺便说一句:我已经有一个

Session
服务的模拟,所以不用担心。

unit-testing angular angular2-routing angular2-testing
13个回答
165
投票

最简单的方法是使用

useValue
属性并提供您想要模拟的值的 Observable。

RxJS < 6

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
...
{
  provide: ActivatedRoute,
  useValue: {
    params: Observable.of({id: 123})
  }
}

RxJS >= 6

import { of } from 'rxjs';
...
{
  provide: ActivatedRoute,
  useValue: {
    params: of({id: 123})
  }
}

44
投票

在 Angular 8+ 中,有

RouterTestingModule
,您可以使用它来访问组件的
ActivatedRoute
Router
。您还可以将路由传递给
RouterTestingModule
并为请求的路由方法创建间谍。

例如在我的组件中我有:

ngOnInit() {
    if (this.route.snapshot.paramMap.get('id')) this.editMode()
    this.titleService.setTitle(`${this.pageTitle} | ${TAB_SUFFIX}`)
}

在我的测试中我有:

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ ProductLinePageComponent ],
      schemas: [NO_ERRORS_SCHEMA],
      imports: [
        RouterTestingModule.withRoutes([])
      ],
    })
    .compileComponents()
  }))

  beforeEach(() => {
    router = TestBed.get(Router)
    route = TestBed.get(ActivatedRoute)
  })

以及稍后的“it”部分:

  it('should update', () => {
    const spyRoute = spyOn(route.snapshot.paramMap, 'get')
    spyRoute.and.returnValue('21')
    fixture = TestBed.createComponent(ProductLinePageComponent)
    component = fixture.componentInstance
    fixture.detectChanges()
    expect(component).toBeTruthy()
    expect(component.pageTitle).toBe('Edit Product Line')
    expect(component.formTitle).toBe('Edit Product Line')
    // here you can test the functionality which is triggered by the snapshot
  })

以类似的方式,我认为您可以通过 jasmine 的

paramMap
方法直接测试
spyOnProperty
,通过返回可观察值或使用 rxjs 弹珠。它可能会节省一些时间,而且不需要维护额外的模拟类。 希望它有用且有意义。


20
投票

我已经知道怎么做了!

由于

ActivatedRoute
是一个服务,因此可以为其建立一个模拟服务。我们称这个模拟服务为
MockActivatedRoute
。我们将在
ActivatedRoute
中扩展
MockActivatedRoute
,如下:

class MockActivatedRoute extends ActivatedRoute {
    constructor() {
        super(null, null, null, null, null);
        this.params = Observable.of({id: "5"});
    }

super(null, ....)
初始化超类,它有四个强制参数。然而,在这种情况下,我们不需要任何这些参数,因此我们将它们初始化为
null
值。我们所需要的只是
params
的值,它是一个
Observable<>
。因此,使用
this.params
,我们覆盖
params
的值并将其初始化为测试对象所依赖的参数的
Observable<>

然后,与任何其他模拟服务一样,只需初始化它并覆盖组件的提供程序即可。

祝你好运!


11
投票

这是我在最新的 Angular 2.0 中测试它的方法...

import { ActivatedRoute, Data } from '@angular/router';

以及提供商部分

{
  provide: ActivatedRoute,
  useValue: {
    data: {
      subscribe: (fn: (value: Data) => void) => fn({
        yourData: 'yolo'
      })
    }
  }
}

8
投票

只需添加 ActivatedRoute 的模拟:

providers: [
  { provide: ActivatedRoute, useClass: MockActivatedRoute }
]

...

class MockActivatedRoute {
  // here you can add your mock objects, like snapshot or parent or whatever
  // example:
  parent = {
    snapshot: {data: {title: 'myTitle ' } },
    routeConfig: { children: { filter: () => {} } }
  };
}

4
投票

在为路由路径创建测试套件时遇到了同样的问题:

{
   path: 'edit/:property/:someId',
   component: YourComponent,
   resolve: {
       yourResolvedValue: YourResolver
   }
}

在组件中,我将传递的属性初始化为:

ngOnInit(): void {    
   this.property = this.activatedRoute.snapshot.params.property;
   ...
}

运行测试时,如果您没有在模拟ActivatedRoute“useValue”中传递属性值,那么在使用“fixture.detectChanges()”检测更改时,您将得到未定义的结果。这是因为 ActivatedRoute 的模拟值不包含属性 params.property。然后,模拟 useValue 需要具有这些参数,以便固定装置初始化组件中的“this.property”。您可以将其添加为:

  let fixture: ComponentFixture<YourComponent>;
  let component: YourComponent;
  let activatedRoute: ActivatedRoute; 

  beforeEach(done => {
        TestBed.configureTestingModule({
          declarations: [YourComponent],
          imports: [ YourImportedModules ],
          providers: [
            YourRequiredServices,
            {
              provide: ActivatedRoute,
              useValue: {
                snapshot: {
                  params: {
                    property: 'yourProperty',
                    someId: someId
                  },
                  data: {
                    yourResolvedValue: { data: mockResolvedData() }
                  }
                }
              }
            }
          ]
        })
          .compileComponents()
          .then(() => {
            fixture = TestBed.createComponent(YourComponent);
            component = fixture.debugElement.componentInstance;
            activatedRoute = TestBed.get(ActivatedRoute);
            fixture.detectChanges();
            done();
          });
      });

您可以开始测试,例如:

it('should ensure property param is yourProperty', async () => {
   expect(activatedRoute.snapshot.params.property).toEqual('yourProperty');
   ....
});

现在,假设您想测试不同的属性值,那么您可以将模拟的ActivatedRoute更新为:

  it('should ensure property param is newProperty', async () => {
    activatedRoute.snapshot.params.property = 'newProperty';
    fixture = TestBed.createComponent(YourComponent);
    component = fixture.debugElement.componentInstance;
    activatedRoute = TestBed.get(ActivatedRoute);
    fixture.detectChanges();

    expect(activatedRoute.snapshot.params.property).toEqual('newProperty');
});

希望这有帮助!


4
投票

角11: 将其添加到您的规范文件中

imports: [
   RouterTestingModule.withRoutes([])
],

这只需一行就可以帮助我解决问题,其他的你需要模拟提供者


4
投票

您可以使用

beforeAll
功能来执行此操作。由于
beforeAll
在所有
beforeEach
函数之前调用,因此您可以在编译组件之前更改成员变量。

describe('MyComponent', () => {

  let fakeActivatedRoute = {
    paramMap: of(convertToParamMap({ id: '123' })),
    queryParamMap: of(convertToParamMap({ query: 'active' }))};


  beforeEach(async() => {
    await TestBed.configureTestingModule({

      providers: [
        ...
        { provide: ActivatedRoute, useValue: fakeActivatedRoute }],
      }).compileComponents();
    });
  });

  describe('id is present in route', () => {
    beforeAll(() => {
      fakeActivatedRoute.paramMap = 
        of(convertToParamMap({ id: '123' }));
      fakeActivatedRoute.queryParamMap = 
        of(convertToParamMap({ query: '' }));
    });

    it('should call service to look up id', () => {
      ...
    });
  });

  describe('id is not present in route', () => {
    beforeAll(() => {
      fakeActivatedRoute.paramMap = 
        of(convertToParamMap({ id: '' }));
      fakeActivatedRoute.queryParamMap = 
        of(convertToParamMap({ query: '' }));
    });

    it('should not call service to look up id', () => {
      ...
    });
  });

  describe('query is present in route', () => {
    beforeAll(() => {
      fakeActivatedRoute.paramMap = 
        of(convertToParamMap({ id: '123' }));
      fakeActivatedRoute.queryParamMap = 
        of(convertToParamMap({ query: 'inactive' }));
    });

    it('should call service to look up the inactive id', () => {
      ...
    });
  });
});


3
投票

对于一些使用 Angular > 5 的人来说,if Observable.of();不起作用,那么他们可以通过从 'rxjs' 导入 import { of } 来使用 of();


0
投票

在测试类中添加提供程序为:

{
  provide: ActivatedRoute,
  useValue: {
    paramMap: of({ get: v => { return { id: 123 }; } })
  } 
}

0
投票

到目前为止,所有其他答案仅提供路由参数的值。如果您想测试路由更改触发器本身怎么办?您可以在测试中为ActivatedRoute提供Subject及其Observable,这样您就可以使用source.next()触发路由更改。

测试中的代码:

    constructor(private readonly route: ActivatedRoute) {}

    ngOnInit(): void {
      this.routeParamSubscription = this.route.params.subscribe((params) => {
        if (params['id']) {
          this.loadDetails(params['id']);
        }
      });
    }

测试代码:

    let routeChangeSource: BehaviorSubject<Params>;
    // In TestBed.configureTestingMethod
    ...
      providers: [
        {
          provide: ActivatedRoute,
          useValue: {
            params: routeChangeSource.asObservable()
          }
        }
      ]
    ...
    it('loads data on route change', fakeAsync(() => {
      const spy = spyOn(component, 'loadDetails').and.callThrough();
      routeChangeSource.next({ id: 99 });
      tick();
      expect(spy).toHaveBeenCalledOnceWith(99);
    }));

这将测试路由更改后触发的操作并确保其已激活。


0
投票

在这种情况下,如果参数是通过

get
paramMap

方法访问的

在.ts中:

this.id= this.activatedRoute.snapshot.paramMap.get('id');

在.spec.ts中

providers: [
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              paramMap: {
                get() {
                  return '97dbf5d7';
                }
              }
            }
          }
        }
      ]

0
投票

2023 年公开

ngOnInit() {
  this.bookId = +this.activatedRoute.snapshot.paramMap.get('bookId');

  this.getBook(this.bookId);
}

对于一个参数

providers: [
   {
    provide: ActivatedRoute,
    useValue: {
      snapshot: {
        paramMap: {
          get: () => 1, // represents the bookId
        },
      },
    },
  },
],

对于两个或多个参数 如果我们只关心 paramMap 中包含的一个参数,就像上面的例子,我们可以直接从 get 方法返回这个参数: get: () => 1。如果我们关心的参数有多个,我们可以使用 switch 语句相反:

  provide: ActivatedRoute,
  useValue: {
    snapshot: {
      paramMap: {
        get: (key: string) => {
          switch (key) {
            case 'bookId':
              return 2;
            case 'genre':
              return 'fiction'
              default:
              return ''
          }
        }
      },
    },
  },
},
© www.soinside.com 2019 - 2024. All rights reserved.