¿Cómo realizar una prueba unitaria de un componente que depende de los parámetros de ActivatedRoute?

93

Estoy probando un componente que se usa para editar un objeto. El objeto tiene una característica única idque se usa para tomar el objeto específico de una matriz de objetos que están alojados en un servicio. El específico idse obtiene a través de un parámetro que se pasa a través del enrutamiento, específicamente a través de la ActivatedRouteclase.

El constructor es el siguiente:

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

Quiero ejecutar pruebas unitarias básicas en este componente. Sin embargo, no estoy seguro de cómo puedo inyectar el idparámetro y el componente necesita este parámetro.

Por cierto: ya tengo una simulación del Sessionservicio, así que no te preocupes.

Colby Cox
fuente

Respuestas:

135

La forma más sencilla de hacer esto es simplemente usar el useValueatributo y proporcionar un Observable del valor que desea simular.

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
fuente
11
Observable.of no existe para mí! : S
Alejandro Sanz Díaz
4
Importar observable de rxjs / Observable
zmanc
6
Este código hace este error en mi proyecto: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 ofdebe usarse solo. También es probable que use en RouterTestingModulelugar del código de esta respuesta.
Ben Racicot
5
@BenRacicot esta respuesta se dio antes de que existiera RxJs 6. Además, en su lugar, decir "haz esto en su lugar" proporciona una respuesta que se puede votar directamente.
zmanc
18

¡He descubierto cómo hacer esto!

Dado que ActivatedRoutees un servicio, se puede establecer un servicio simulado. Llamemos a este servicio simulado MockActivatedRoute. Extenderemos ActivatedRouteen MockActivatedRoute, de la siguiente manera:

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

La línea super(null, ....)inicializa la superclase, que tiene cuatro parámetros obligatorios. Sin embargo, en este caso, no necesitamos nada de ninguno de estos parámetros, por lo que los inicializamos a nullvalores. Todo lo que necesitamos es paramscuyo valor es un Observable<>. Por lo tanto, con this.params, anulamos el valor de paramsy lo inicializamos para que sea elObservable<> del parámetro en el que se basa el sujeto de prueba.

Luego, como cualquier otro servicio simulado, simplemente inicialícelo y anule el proveedor del componente.

¡Buena suerte!

Colby Cox
fuente
1
¡Estoy enfrentando esto ahora mismo! Sin embargo, obtengo errores cuando intento usar supero Observable. ¿De dónde vienen estos?
Aarmora
super()está integrado. Observablees de rxjs/Observableo simplemente rxjsdepende de su versión. Lo conseguirías usando import {Observable} from 'rxjs'.
oooyaya
Aceptaste una respuesta y publicaste otra ... si se trataba de Highlander (y solo podría haber una), ¿cuál elegiste "realmente" y por qué? Es decir, creo que esto esencialmente se reduce a lo mismo que la respuesta de zmanc, que aceptó. ¿Encontraste algún valor adicional al configurar este simulacro [un poco] más complicado?
ruffin
11

En angular 8+ está el RouterTestingModule, que puede utilizar para tener acceso a ActivatedRoute o Router del componente. También puede pasar rutas al RouterTestingModule y crear espías para los métodos de ruta solicitados.

Por ejemplo, en mi componente tengo:

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

Y en mi prueba tengo:

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

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

y más tarde en la sección 'eso':

  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 manera similar, creo que puede probar directamente el paramMap a través del método spyOnProperty de jazmín, devolviendo un observable o usando canicas rxjs. Puede ahorrar algo de tiempo y tampoco requiere mantener una clase simulada adicional. Espero que sea útil y tenga sentido.

dimitris maf
fuente
Mucho mejor que tener que mantener un simulacro adicional y puedes configurar fácilmente diferentes parámetros en las pruebas. ¡Gracias!
migg
Esto ayuda. ¿Sabe cómo espiar diferentes parámetros? Const dirName = this.route.snapshot.paramMap.get ('dirName'); const actionType = this.route.snapshot.paramMap.get ('actionType'); ¿En cuál de los bots espiará spyOn (route.snapshot.paramMap, 'get')? ¿Puedo especificar la clave para escuchar?
speksy
Como mencioné anteriormente, creo que podría usar spyOnProperty en lugar de spyOn, por ejemplo, spyOnProperty (route.snapshot.paramMap.get, 'dirName'). Si no he respondido completamente a tu pregunta, no dudes en decírmelo. Gracias.
dimitris maf
10

Así es como lo probé en angular 2.0 más reciente ...

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

y en la sección Proveedores

{
  provide: ActivatedRoute,
  useValue: {
    data: {
      subscribe: (fn: (value: Data) => void) => fn({
        yourData: 'yolo'
      })
    }
  }
}
Rady
fuente
¿Puede proporcionar el código completo de la sección de proveedores?
Michael JDI
Esta es una clase de prueba unitaria completa. plnkr.co/edit/UeCKnJ2sCCpLLQcWqEGX?p=catalogue
Rady
¿Cómo se prueba la cancelación de suscripción en ngOnDestroy
Shiva
Esto se interrumpirá en un caso de uso de la vida real porque no está devolviendo una suscripción y no podrá usar la llamada .unsubscribe () en ngOnDestroy.
Quovadisqc
1
data: Observable.of ({yourData: 'yolo'}) funcionaría sin embargo.
Quovadisqc
4

Simplemente agregue una simulación 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
fuente
3

Para algunas personas que trabajan en Angular> 5, if Observable.of (); no funciona, entonces pueden usar solo of () importando import {of} de 'rxjs';

Shashank Sharma
fuente
1

Me encontré con el mismo problema al crear conjuntos de pruebas para una ruta de enrutamiento como:

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

En el componente, inicialicé la propiedad pasada como:

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

Cuando ejecute las pruebas, si no pasa un valor de propiedad en su Mock ActivatedRoute "useValue", entonces no estará definido cuando detecte cambios usando "fixture.detectChanges ()". Esto se debe a que los valores simulados de ActivatedRoute no contienen la propiedad params.property. Luego, es necesario que useValue simulado tenga esos parámetros para que el dispositivo inicialice 'this.property' en el componente. Puedes agregarlo como:

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

Puede comenzar a probar como, por ejemplo:

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

Ahora, digamos que le gustaría probar un valor de propiedad diferente, luego puede actualizar su Mock ActivatedRoute como:

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

¡Espero que esto ayude!

Diego Herrera
fuente
0

Proveedor agregado en la clase de prueba como:

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