Як модульно протестувати компонент, який залежить від параметрів з 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

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

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

Ви прийняли одну відповідь і опублікували іншу ... якщо це був Горець (а міг бути лише один), яку з них Ви "справді" вибрали і чому? Тобто, я думаю, це по суті зводиться до того самого, що і відповідь намаца, яку ви прийняли. Ви знайшли додаткове значення від налаштування цього [трохи] більш складного макету?
jorffin

11

У кутовій версії 8+ є RouterTestingModule, який ви можете використовувати для того, щоб мати доступ до ActivateRoute або маршрутизатора компонента. Також ви можете передавати маршрути до 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
  })

Подібним чином, я думаю, ви можете протестувати безпосередньо paramMap за допомогою методу spyOnProperty жасмину, повернувши спостережуваний або використовуючи rxjs мармурові. Це може заощадити трохи часу, а також не вимагає підтримки додаткового макетного класу. Сподіваюся, що це корисно і має сенс.


Набагато краще, ніж необхідність підтримувати додатковий макет, і ви можете легко встановити різні параметри в тестах. Дякую!
migg

Це допомагає. Чи знаєте ви, як підглядати різні параметри: const dirName = this.route.snapshot.paramMap.get ('dirName'); const actionType = this.route.snapshot.paramMap.get ('actionType'); На якому з ботів буде шпигувати spyOn (route.snapshot.paramMap, 'get')? Чи можу я вказати ключ для прослуховування?
speksy

Як я вже згадував вище, я думаю, ви можете використовувати spyOnProperty замість spyOn, наприклад spyOnProperty (route.snapshot.paramMap.get, 'dirName'). Якщо я не відповів повністю на ваше запитання, не соромтеся сказати мені. Дякую.
dimitris maf

10

Ось як я тестував це в останній версії 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
Шива

Це призведе до порушення в реальному житті, оскільки ви не повертаєте підписку і не зможете використовувати виклик .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 (); не працює, тоді вони можуть використовувати just 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 ()". Це тому, що фіктивні значення для ActivatedRoute не містять властивості params.property. Потім для mock 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');
   ....
});

Тепер, скажімо, ви хочете протестувати інше значення властивості, тоді ви можете оновити свій макет ActivateRoute як:

  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.