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

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


person Colby Cox    schedule 13.07.2016    source источник


Ответы (8)


Самый простой способ сделать это - просто использовать атрибут 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})
  }
}
person zmanc    schedule 06.12.2016
comment
Observable.of для меня не существует! : S - person Alejandro Sanz Díaz; 26.12.2016
comment
Импортировать Observable из rxjs / Observable - person zmanc; 26.12.2016
comment
Этот код вызывает ошибку в моем проекте: 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 - person mixalbl4; 08.08.2017
comment
@MixerOID, это баги, поедающие хром - поместите тест в try-catch, и вы должны увидеть ошибку. - person a3uge; 15.08.2017
comment
Я знаю, что это 2 года спустя ... но если вы получаете сообщение об ошибке Uncaught NetworkError, опубликованной MixerOID, убедитесь, что вы используете правильный тип параметров. Моя проблема заключалась в том, что мой код ожидал queryParams, а не params. - person adamdabbracci; 29.11.2017
comment
Чтобы избежать общего Uncaught NetworkError..., вы также можете отключить исходные карты, запустив тесты с ng test --sourcemaps=false - тогда вы должны увидеть настоящую ошибку, вызывающую проблему. - person danwellman; 04.03.2018
comment
{provide: ActivatedRoute, useValue: {snapshot: {params: Observable.of ({userName: 'testUser', emailValidationToken: 'token'})}}} - person Michael Dausmann; 22.04.2018
comment
RxJs 6 of следует использовать отдельно. Также вы, вероятно, использовали бы RouterTestingModule вместо этого кода ответа. - person Ben Racicot; 21.10.2018
comment
@BenRacicot этот ответ был дан до того, как существовал RxJs 6. Также вместо того, чтобы сказать, сделайте это, вместо этого предоставьте ответ, за который можно проголосовать напрямую. - person zmanc; 01.11.2018

В 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 с помощью spyOnProperty метода jasmine, возвращая наблюдаемое или используя rxjs marbles. Это может сэкономить время, а также не требует поддержки дополнительного фиктивного класса. Надеюсь, что это будет полезно и имеет смысл.

person dimitris maf    schedule 03.12.2019
comment
Это намного лучше, чем необходимость поддерживать дополнительный макет, и вы можете легко устанавливать различные параметры в тестах. Спасибо! - person crackmigg; 16.01.2020
comment
Это помогает. Вы знаете, как отслеживать различные параметры: const dirName = this.route.snapshot.paramMap.get ('dirName'); const actionType = this.route.snapshot.paramMap.get ('actionType'); За кем из ботов будет шпионить spyOn (route.snapshot.paramMap, 'get')? Могу я указать ключ для прослушивания? - person speksy; 05.04.2020
comment
Как я упоминал выше, я думаю, вы могли бы использовать spyOnProperty вместо spyOn, например spyOnProperty (route.snapshot.paramMap.get, 'dirName'). Если я не ответил полностью на ваш вопрос, не стесняйтесь сказать мне. Спасибо. - person dimitris maf; 06.04.2020

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

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

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

Удачи!

person Colby Cox    schedule 13.07.2016
comment
Я столкнулся с этим прямо сейчас! Однако я получаю ошибки, когда пытаюсь использовать super или Observable. Откуда они берутся? - person Aarmora; 14.07.2016
comment
super() встроен. Observable от rxjs/Observable или просто rxjs в зависимости от вашей версии. Вы получите это с помощью import {Observable} from 'rxjs'. - person oooyaya; 22.03.2017
comment
Вы приняли один ответ и отправили другой ... если это был Горец (а мог быть только один), какой именно вы выбрали и почему? То есть, я думаю, это по сути сводится к тому же, что и ответ zmanc, который вы приняли. Вы нашли дополнительную ценность в настройке этого [немного] более сложного макета? - person ruffin; 11.10.2019

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

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

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

{
  provide: ActivatedRoute,
  useValue: {
    data: {
      subscribe: (fn: (value: Data) => void) => fn({
        yourData: 'yolo'
      })
    }
  }
}
person Rady    schedule 15.11.2016
comment
Можете ли вы предоставить полный код для раздела провайдеров? - person Michael JDI; 29.11.2016
comment
Это полный класс модульного тестирования. plnkr.co/edit/UeCKnJ2sCCpLLQcWqEGX?p=catalogue - person Rady; 02.12.2016
comment
Как вы тестируете отписку в ngOnDestroy - person shiva; 02.02.2017
comment
Это сломается в реальной жизни, потому что вы не возвращаете подписку и не сможете использовать call .unsubscribe () в ngOnDestroy. - person Quovadisqc; 20.02.2017
comment
data: Observable.of ({yourData: 'yolo'}) будет работать. - person Quovadisqc; 20.02.2017
comment
Я согласен с @Quovadisqc, что вы НЕ должны высмеивать классы вне своей программы. Это хорошее решение, когда вы заменяете подписку на Observable.of - person ThetaSinner; 11.06.2018

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

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

person Shashank Sharma    schedule 13.08.2019

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

{
   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');
});

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

person Diego Herrera    schedule 05.06.2020

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

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