Jak przetestować jednostkę, która zależy od parametrów z ActivatedRoute?

93

Testuję jednostkę, która jest używana do edycji obiektu. Obiekt ma unikat idużywany do pobierania określonego obiektu z tablicy obiektów hostowanych w usłudze. Specyfika idjest uzyskiwana za pośrednictwem parametru przekazywanego za pośrednictwem routingu, w szczególności przez ActivatedRouteklasę.

Konstruktor wygląda następująco:

 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];

Chcę uruchomić podstawowe testy jednostkowe tego składnika. Nie jestem jednak pewien, jak mogę wstrzyknąć idparametr, a komponent potrzebuje tego parametru.

Swoją drogą: mam już makietę do Sessionusługi, więc nie martw się.

Colby Cox
źródło

Odpowiedzi:

135

Najprostszym sposobem, aby to zrobić, jest użycie useValueatrybutu i dostarczenie Observable wartości, którą chcesz mockować.

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})
  }
}
zmanc
źródło
11
Observable.of dla mnie nie istnieje! : S
Alejandro Sanz Díaz
4
Import Observable from rxjs / Observable
zmanc
6
Ten kod powoduje ten błąd w moim projekcie: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 ofpowinien być używany samodzielnie. Prawdopodobnie użyłbyś również RouterTestingModulezamiast kodu tej odpowiedzi.
Ben Racicot
5
@BenRacicot ta odpowiedź została udzielona zanim istniał RxJs 6. Zamiast tego powiedz „zamiast tego”, podaj odpowiedź, na którą można bezpośrednio zagłosować.
zmanc
18

Wymyśliłem, jak to zrobić!

Ponieważ ActivatedRoutejest to usługa, można ustanowić dla niej usługę pozorowaną. Nazwijmy to pozorowaną usługą MockActivatedRoute. Rozszerzymy ActivatedRoutew MockActivatedRoutenastępujący sposób:

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

Linia super(null, ....)inicjuje superklasę, która ma cztery obowiązkowe parametry. Jednak w tym przypadku nie potrzebujemy niczego z żadnego z tych parametrów, więc inicjalizujemy je do nullwartości. Potrzebujemy tylko wartości, paramsktóra ma wartość Observable<>. Dlatego za pomocą this.paramsnadpisujemy wartość parametru paramsi inicjalizujemy go jako Observable<>parametr, na którym opiera się podmiot testowy.

Następnie, tak jak każda inna usługa pozorowana, po prostu ją zainicjuj i zastąp dostawcę komponentu.

Powodzenia!

Colby Cox
źródło
1
Stoję przed tym teraz! Jednak podczas próby użycia superlub Observable. Skąd się one biorą?
Aarmora
super()jest wbudowany. Observablepochodzi z rxjs/Observablelub tylko w rxjszależności od wersji. Dostałbyś to za pomocą import {Observable} from 'rxjs'.
oooyaya
Zaakceptowałeś jedną odpowiedź i opublikowałeś inną… jeśli to był Highlander (a mógł być tylko jeden), którą z nich „naprawdę” wybrałeś i dlaczego? To znaczy, myślę, że sprowadza się to zasadniczo do tego samego, co odpowiedź zmanc, którą zaakceptowałeś. Czy znalazłeś dodatkową wartość w ustawieniu tej [nieco] bardziej skomplikowanej makiety?
ruffin
11

W kątowej 8+ jest RouterTestingModule, którego możesz użyć, aby uzyskać dostęp do ActivatedRoute lub Router komponentu. Możesz także przekazywać trasy do RouterTestingModule i tworzyć szpiegów dla żądanych metod tras.

Na przykład w moim komponencie mam:

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

W moim teście mam:

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

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

a później w sekcji „to”:

  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
  })

W podobny sposób myślę, że możesz bezpośrednio przetestować paramMap za pomocą metody spyOnProperty jaśminu, zwracając obserwowalne lub używając kulek rxjs. Może to zaoszczędzić trochę czasu, a także nie wymaga utrzymywania dodatkowej klasy próbnej. Mam nadzieję, że jest to przydatne i ma sens.

dimitris maf
źródło
O wiele lepsze niż konieczność utrzymywania dodatkowej makiety i można łatwo ustawić różne parametry w testach. Dziękuję Ci!
migg
To pomaga. Czy wiesz, jak szpiegować różne parametry: const dirName = this.route.snapshot.paramMap.get ('dirName'); const actionType = this.route.snapshot.paramMap.get ('actionType'); Na którym z botów będzie szpiegować spyOn (route.snapshot.paramMap, 'get')? Czy mogę określić klucz do słuchania?
speksy
Jak wspomniałem powyżej, myślę, że można użyć spyOnProperty zamiast spyOn, na przykład spyOnProperty (route.snapshot.paramMap.get, „dirName”). Jeśli nie odpowiedziałem całkowicie na Twoje pytanie, nie wahaj się i powiedz mi. Dzięki.
Dimitris maf
10

Oto jak przetestowałem to w najnowszym Angular 2.0 ...

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

oraz w sekcji Dostawcy

{
  provide: ActivatedRoute,
  useValue: {
    data: {
      subscribe: (fn: (value: Data) => void) => fn({
        yourData: 'yolo'
      })
    }
  }
}
Rady
źródło
Czy możesz podać pełny kod dla sekcji dostawców?
Michael JDI
To jest pełna klasa testów jednostkowych. plnkr.co/edit/UeCKnJ2sCCpLLQcWqEGX?p=catalogue
Rady
Jak przetestować anulowanie subskrypcji w ngOnDestroy
shiva
To zepsuje się w prawdziwym życiu, ponieważ nie zwrócisz subskrypcji i nie będziesz mógł użyć wywołania .unsubscribe () w ngOnDestroy.
Quovadisqc
1
data: Observable.of ({yourData: 'yolo'}) działałoby jednak.
Quovadisqc
4

Po prostu dodaj makietę 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: () => {} } }
  };
}
Francesco Borzi
źródło
3

Dla niektórych osób pracujących na Angular> 5, jeśli Observable.of (); nie działa, mogą użyć samej funkcji () importując import {of} z 'rxjs';

Shashank Sharma
źródło
1

Podczas tworzenia zestawów testów dla ścieżki routingu napotkałem ten sam problem, co:

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

W komponencie zainicjowałem przekazaną właściwość jako:

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

Podczas uruchamiania testów, jeśli nie przekażesz wartości właściwości w swojej makiecie ActivatedRoute „useValue”, podczas wykrywania zmian za pomocą metody „fixture.detectChanges ()” uzyskasz wartość niezdefiniowaną. Dzieje się tak, ponieważ pozorowane wartości dla ActivatedRoute nie zawierają właściwości params.property. Następnie pozorna wartość useValue musi mieć te parametry, aby urządzenie mogło zainicjować właściwość „this.właściwość” w komponencie. Możesz dodać go jako:

  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();
          });
      });

Możesz rozpocząć testowanie, na przykład:

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

Teraz powiedzmy, że chcesz przetestować inną wartość właściwości, a następnie możesz zaktualizować swoją próbną trasę ActivatedRoute jako:

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

Mam nadzieję że to pomoże!

Diego Herrera
źródło
0

Dodano dostawcę w klasie testowej jako:

{
  provide: ActivatedRoute,
  useValue: {
    paramMap: of({ get: v => { return { id: 123 }; } })
  } 
}
adelinor
źródło