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

How to unit test a component that depends on parameters from ActivatedRoute?

我正在对用于编辑对象的组件进行单元测试。 该对象具有唯一的id,用于从服务中托管的对象数组中捕获特定对象。 特定的id通过通过路由传递的参数(特别是通过ActivatedRoute类)获得。

构造函数如下:

1
2
3
4
5
6
7
 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服务提供了一个模拟,因此不用担心。


最简单的方法是使用useValue属性,并提供要模拟的值的Observable。

RxJS <6

1
2
3
4
5
6
7
8
9
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
...
{
  provide: ActivatedRoute,
  useValue: {
    params: Observable.of({id: 123})
  }
}

RxJS> = 6

1
2
3
4
5
6
7
8
import { of } from 'rxjs';
...
{
  provide: ActivatedRoute,
  useValue: {
    params: of({id: 123})
  }
}


我已经知道如何做到这一点!

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

1
2
3
4
5
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<>

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

祝好运!


这是我在最新的angular 2.0中对其进行测试的方法...

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

和在提供商部分

1
2
3
4
5
6
7
8
9
10
{
  provide: ActivatedRoute,
  useValue: {
    data: {
      subscribe: (fn: (value: Data) => void) => fn({
        yourData: 'yolo'
      })
    }
  }
}


在angular 8+中,提供了RouterTestingModule,您可以使用该模块来访问组件的ActivatedRoute或Router。您也可以将路由传递到RouterTestingModule并为请求的路由方法创建间谍。

例如,在我的组件中,我有:

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

在我的测试中,我有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ ProductLinePageComponent ],
      schemas: [NO_ERRORS_SCHEMA],
      imports: [
        RouterTestingModule.withRoutes([])
      ],
    })
    .compileComponents()
  }))

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

然后在" it"部分中:

1
2
3
4
5
6
7
8
9
10
11
  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
  })

以类似的方式,我认为您可以通过返回一个可观察的对象或使用rxjs大理石通过茉莉花的spyOnProperty方法直接测试paramMap。这可能会节省一些时间,也不需要维护额外的模拟类。
希望它有用并且有意义。


只需添加一个ActivatedRoute的模拟:

1
2
3
providers: [
  { provide: ActivatedRoute, useClass: MockActivatedRoute }
]

...

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

对于某些使用Angular> 5的人,如果Observable.of();无法工作,那么他们可以通过从'rxjs'导入import {of}来仅使用of();


为路由路径创建测试套件时遇到以下相同问题:

1
2
3
4
5
6
7
{
   path: 'edit/:property/:someId',
   component: YourComponent,
   resolve: {
       yourResolvedValue: YourResolver
   }
}

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

1
2
3
4
ngOnInit(): void {    
   this.property = this.activatedRoute.snapshot.params.property;
   ...
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  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();
          });
      });

您可以开始测试,例如:

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

现在,假设您要测试其他属性值,然后可以将模拟的ActivatedRoute更新为:

1
2
3
4
5
6
7
8
9
  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');
});

希望这可以帮助!


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

1
2
3
4
5
6
{
  provide: ActivatedRoute,
  useValue: {
    paramMap: of({ get: v => { return { id: 123 }; } })
  }
}