Как выполнить модульное тестирование компонента, зависящего от параметров ActivatedRoute?


93

Я тестирую модуль, который используется для редактирования объекта. У объекта есть уникальный объект, 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сервиса, так что не беспокойтесь.

Ответы:


135

Самый простой способ сделать это - просто использовать 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})
  }
}

11
Observable.of для меня не существует! : S
Алехандро Санс Диас

4
Импортировать Observable из rxjs / Observable
zmanc

6
Этот код вызывает эту ошибку в моем проекте:Uncaught NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'ng:///DynamicTestModule/HomeContentComponent.ngfactory.js'. at http://localhost:9876/_karma_webpack_/polyfills.bundle.js:2605
MixerOID

5
RxJs 6 ofследует использовать отдельно. Также вы, вероятно, использовали бы RouterTestingModuleвместо этого кода ответа.
Ben Racicot

5
@BenRacicot этот ответ был дан до того, как появился RxJs 6. Также вместо того, чтобы сказать «сделай это вместо этого», предоставьте ответ, за который можно проголосовать напрямую.
zmanc 01

18

Я понял, как это сделать!

Так 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<>как параметр, на который полагается испытуемый.

Затем, как и любой другой фиктивный сервис, просто инициализируйте его и переопределите поставщика для компонента.

Удачи!


1
Я столкнулся с этим прямо сейчас! Однако я получаю ошибки, когда пытаюсь использовать superили Observable. Откуда они берутся?
Aarmora

super()встроен. Observableот rxjs/Observableили только в rxjsзависимости от вашей версии. Вы бы это использовали import {Observable} from 'rxjs'.
oooyaya

Вы приняли один ответ и отправили другой ... если это был Горец (а мог быть только один), какой из них вы «на самом деле» выбрали и почему? То есть я думаю, что это по сути сводится к тому же, что и ответ zmanc, который вы приняли. Вы нашли дополнительную ценность в настройке этого [немного] более сложного макета?
ruffin

11

В 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('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
  })

Подобным образом, я думаю, вы можете напрямую протестировать paramMap с помощью метода jasmine spyOnProperty, возвращая наблюдаемый объект или используя rxjs marbles. Это может сэкономить время, а также не требует поддержки дополнительного фиктивного класса. Надеюсь, что это полезно и имеет смысл.


Намного лучше, чем поддерживать лишний макет, и вы можете легко устанавливать различные параметры в тестах. Спасибо!
migg

Это помогает. Вы знаете, как отслеживать различные параметры: const dirName = this.route.snapshot.paramMap.get ('dirName'); const actionType = this.route.snapshot.paramMap.get ('actionType'); За кем из ботов будет шпионить spyOn (route.snapshot.paramMap, 'get')? Могу я указать ключ для прослушивания?
спексы

Как я упоминал выше, я думаю, вы могли бы использовать spyOnProperty вместо spyOn, например spyOnProperty (route.snapshot.paramMap.get, 'dirName'). Если я не ответил полностью на ваш вопрос, не стесняйтесь сказать мне. Спасибо.
димитрис маф

10

Вот как я тестировал это в последней версии angular 2.0 ...

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

и в разделе "Провайдеры"

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

Можете ли вы предоставить полный код для раздела провайдеров?
Michael JDI

Это полный класс модульного тестирования. plnkr.co/edit/UeCKnJ2sCCpLLQcWqEGX?p=catalogue
Рэди

Как вы тестируете отписку в ngOnDestroy
shiva

Это сломается в реальной жизни, потому что вы не возвращаете подписку и не сможете использовать call .unsubscribe () в ngOnDestroy.
Quovadisqc

1
data: Observable.of ({yourData: 'yolo'}) будет работать.
Quovadisqc

4

Просто добавьте макет 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: () => {} } }
  };
}

3

Для некоторых людей, работающих с Angular> 5, если Observable.of (); не работает, то они могут использовать только of (), импортировав import {of} из 'rxjs';


1

Решите ту же проблему при создании наборов тестов для пути маршрутизации:

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

В компоненте я инициализировал переданное свойство как:

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

Если при запуске тестов вы не передадите значение свойства в своем имитирующем ActivatedRoute «useValue», то при обнаружении изменений с помощью «fixture.detectChanges ()» вы получите значение undefined. Это связано с тем, что фиктивные значения для 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');
});

Надеюсь это поможет!


0

Добавлен провайдер в тестовый класс как:

{
  provide: ActivatedRoute,
  useValue: {
    paramMap: of({ get: v => { return { id: 123 }; } })
  } 
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.