Comment tester unitaire un composant qui dépend des paramètres d'ActivatedRoute?

93

Je teste en bloc un composant utilisé pour éditer un objet. L'objet a un unique idqui est utilisé afin de récupérer l'objet spécifique d'un tableau d'objets hébergés dans un service. Le spécifique idest obtenu via un paramètre qui est passé via le routage, en particulier via la ActivatedRouteclasse.

Le constructeur est le suivant:

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

Je souhaite exécuter des tests unitaires de base sur ce composant. Cependant, je ne suis pas sûr de la façon dont je peux injecter le idparamètre, et le composant a besoin de ce paramètre.

Au fait: j'ai déjà une maquette pour le Sessionservice, donc pas de soucis là-bas.

Colby Cox
la source

Réponses:

135

Le moyen le plus simple de le faire est d'utiliser simplement l' useValueattribut et de fournir un observable de la valeur que vous souhaitez simuler.

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
la source
11
Observable. De n'existe pas pour moi! : S
Alejandro Sanz Díaz
4
Importer Observable de rxjs / Observable
zmanc
6
Ce code fait cette erreur dans mon projet: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 ofdoit être utilisé seul. Vous utiliserez probablement aussi à la RouterTestingModuleplace du code de cette réponse.
Ben Racicot
5
@BenRacicot cette réponse a été donnée avant que RxJs 6 n'existe. Au lieu de cela, dire «faites-le plutôt» fournit une réponse qui peut être votée directement.
zmanc
18

J'ai compris comment faire cela!

Puisqu'il ActivatedRoutes'agit d'un service, un service simulé peut être établi. Appelons ce service simulé MockActivatedRoute. Nous prolongerons ActivatedRoutedans MockActivatedRoute, comme suit:

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

La ligne super(null, ....)initialise la super classe, qui a quatre paramètres obligatoires. Cependant, dans ce cas, nous n'avons besoin d'aucun de ces paramètres, nous les initialisons donc avec des nullvaleurs. Tout ce dont nous avons besoin est dont la valeur paramsest un Observable<>. Par conséquent, avec this.params, nous remplaçons la valeur de paramset l'initialisons comme étant Observable<>le paramètre sur lequel le sujet de test s'appuie.

Ensuite, comme tout autre service fictif, initialisez-le et remplacez le fournisseur du composant.

Bonne chance!

Colby Cox
la source
1
Je suis confronté à cela en ce moment! Cependant, j'obtiens des erreurs lorsque j'essaye d'utiliser superou Observable. D'où viennent ceux là?
Aarmora le
super()est intégré. Observableest de rxjs/Observableou simplement en rxjsfonction de votre version. Vous l'obtiendrez en utilisant import {Observable} from 'rxjs'.
oooyaya
Vous avez accepté une réponse et en avez posté une autre ... si c'était Highlander (et il ne pouvait y en avoir qu'une), laquelle avez-vous "vraiment" choisi et pourquoi? Autrement dit, je pense que cela se réduit essentiellement à la même chose que la réponse de zmanc, que vous avez acceptée. Avez-vous trouvé une valeur supplémentaire à la mise en place de cette maquette [légèrement] plus compliquée?
ruffin
11

Dans angular 8+ il y a le RouterTestingModule, que vous pouvez utiliser pour avoir accès à l'ActivatedRoute ou au Router du composant. Vous pouvez également transmettre des routes au RouterTestingModule et créer des espions pour les méthodes de route demandées.

Par exemple dans mon composant j'ai:

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

Et dans mon test j'ai:

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

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

et plus tard dans la section «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
  })

De la même manière, je pense que vous pouvez tester directement la paramMap via la méthode spyOnProperty de jasmine, en renvoyant un observable ou en utilisant des billes rxjs. Cela peut gagner du temps et ne nécessite pas non plus de maintenir une classe fictive supplémentaire. J'espère que c'est utile et logique.

dimitris maf
la source
Tellement mieux que d'avoir à maintenir une maquette supplémentaire et vous pouvez facilement définir différents paramètres dans les tests. Merci!
migg le
CA aide. Savez-vous comment espionner différents paramètres: const dirName = this.route.snapshot.paramMap.get ('dirName'); const actionType = this.route.snapshot.paramMap.get ('actionType'); Sur lequel des robots espionnera spyOn (route.snapshot.paramMap, 'get')? Puis-je spécifier la clé à écouter?
speksy
Comme je l'ai mentionné ci-dessus, je pense que vous pouvez utiliser spyOnProperty au lieu de spyOn, par exemple spyOnProperty (route.snapshot.paramMap.get, 'dirName'). Si je n'ai pas répondu complètement à votre question, n'hésitez pas à me le dire. Merci.
dimitris maf le
10

Voici comment je l'ai testé dans la dernière version angulaire 2.0 ...

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

et dans la section Fournisseurs

{
  provide: ActivatedRoute,
  useValue: {
    data: {
      subscribe: (fn: (value: Data) => void) => fn({
        yourData: 'yolo'
      })
    }
  }
}
Rady
la source
Pouvez-vous fournir le code complet de la section des fournisseurs?
Michael JDI
Ceci est une classe de test unitaire complète. plnkr.co/edit/UeCKnJ2sCCpLLQcWqEGX?p=catalogue
Rady
Comment tester le désabonnement dans ngOnDestroy
Shiva
Cela se cassera dans un cas d'utilisation réel car vous ne renvoyez pas d'abonnement et vous ne pourrez pas utiliser l'appel .unsubscribe () dans ngOnDestroy.
Quovadisqc
1
data: Observable.of ({yourData: 'yolo'}) fonctionnerait cependant.
Quovadisqc
4

Ajoutez simplement une maquette de 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
la source
3

Pour certaines personnes travaillant sur Angular> 5, si Observable.of (); ne fonctionne pas alors ils peuvent utiliser juste of () en important import {of} depuis 'rxjs';

Shashank Sharma
la source
1

Ran dans le même problème lors de la création de suites de tests pour un chemin de routage comme:

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

Dans le composant, j'ai initialisé la propriété passée comme suit:

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

Lors de l'exécution des tests, si vous ne transmettez pas une valeur de propriété dans votre simulation ActivatedRoute "useValue", alors vous obtiendrez undefined lors de la détection des modifications en utilisant "fixture.detectChanges ()". Cela est dû au fait que les valeurs fictives pour ActivatedRoute ne contiennent pas la propriété params.property. Ensuite, il est nécessaire que la valeur useValue fictive ait ces paramètres pour que le projecteur initialise «this.property» dans le composant. Vous pouvez l'ajouter comme:

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

Le vous pouvez commencer à tester comme par exemple:

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

Maintenant, disons que vous souhaitez tester une valeur de propriété différente, vous pouvez alors mettre à jour votre maquette ActivatedRoute comme:

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

J'espère que cela t'aides!

Diego Herrera
la source
0

Ajout du fournisseur dans la classe de test comme:

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