Como testar a unidade de um componente que depende de parâmetros de ActivatedRoute?

94

Estou testando uma unidade de um componente que é usado para editar um objeto. O objeto tem um único idque é usado para capturar o objeto específico de uma matriz de objetos hospedados em um serviço. O específico idé obtido por meio de um parâmetro que é passado por meio de roteamento, especificamente por meio da ActivatedRouteclasse.

O construtor é o seguinte:

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

Quero executar testes de unidade básicos neste componente. No entanto, não tenho certeza de como posso injetar o idparâmetro, e o componente precisa desse parâmetro.

A propósito: já tenho um mock para o Sessionserviço, então não se preocupe.

Colby Cox
fonte

Respostas:

138

A maneira mais simples de fazer isso é apenas usar o useValueatributo e fornecer um Observable do valor que você deseja 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
fonte
11
Observable.of não existe para mim! : S
Alejandro Sanz Díaz
4
Importar observável de rxjs / observável
zmanc
6
Este código apresenta este erro no meu projeto: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 ofdeve ser usado sozinho. Além disso, você provavelmente usaria em RouterTestingModulevez do código desta resposta.
TR3B de
5
@BenRacicot esta resposta foi dada antes de RxJs 6 existir. Também, em vez de dizer "faça isso", forneça uma resposta que pode ser votada diretamente a favor.
zmanc 01 de
19

Eu descobri como fazer isso!

Por ActivatedRouteser um serviço, um serviço simulado para ele pode ser estabelecido. Vamos chamar esse serviço simulado MockActivatedRoute. Vamos estender ActivatedRouteem MockActivatedRoute, como segue:

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

A linha super(null, ....)inicializa a superclasse, que possui quatro parâmetros obrigatórios. No entanto, neste caso, não precisamos de nada de nenhum desses parâmetros, então os inicializamos com nullvalores. Tudo o que precisamos é o valor do paramsqual é um Observable<>. Portanto, com this.params, substituímos o valor de paramse o inicializamos para ser o Observable<>do parâmetro no qual o sujeito de teste está contando.

Então, como qualquer outro serviço simulado, apenas inicialize-o e substitua o provedor do componente.

Boa sorte!

Colby Cox
fonte
1
Estou enfrentando isso agora! No entanto, estou recebendo erros quando tento usar superou Observable. De onde vêm estes?
Aarmora
super()está integrado. Observableé de rxjs/Observableou apenas rxjsdependendo da sua versão. Você conseguiria usando import {Observable} from 'rxjs'.
oooyaya
Você aceitou uma resposta e postou outra ... se fosse Highlander (e só poderia haver um), qual você "realmente" escolheu e por quê? Ou seja, acho que isso se reduz essencialmente à mesma coisa que a resposta de zmanc, que você aceitou. Você encontrou valor adicional na configuração deste mock [um pouco] mais complicado?
ruffin de
13

No angular 8+ existe o RouterTestingModule, que você pode usar para ter acesso ao ActivatedRoute ou Roteador do componente. Além disso, você pode passar rotas para o RouterTestingModule e criar espiões para os métodos de rota solicitados.

Por exemplo, em meu componente eu tenho:

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

E em meu teste eu tenho:

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

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

e mais tarde na seção 'isso':

  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 forma semelhante, acho que você pode testar diretamente o paramMap através do método spyOnProperty do jasmine, retornando um observável ou usando mármores rxjs. Isso pode economizar algum tempo e também não requer a manutenção de uma classe extra de simulação. Espero que seja útil e faça sentido.

dimitris maf
fonte
Muito melhor do que manter um mock extra e você pode facilmente definir diferentes parâmetros nos testes. Obrigado!
migg de
Isso ajuda. Você sabe como espionar parâmetros diferentes: const dirName = this.route.snapshot.paramMap.get ('dirName'); const actionType = this.route.snapshot.paramMap.get ('actionType'); Em qual bot irá espionar spyOn (route.snapshot.paramMap, 'get')? Posso especificar a chave para ouvir?
speksy de
Como mencionei acima, acho que você poderia usar spyOnProperty em vez de spyOn, por exemplo spyOnProperty (route.snapshot.paramMap.get, 'dirName'). Se eu não respondi sua pergunta completamente, não hesite em me dizer. Obrigado.
dimitris maf
10

Aqui está como eu testei no angular 2.0 mais recente ...

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

e na seção Provedores

{
  provide: ActivatedRoute,
  useValue: {
    data: {
      subscribe: (fn: (value: Data) => void) => fn({
        yourData: 'yolo'
      })
    }
  }
}
Rady
fonte
Você pode fornecer o código completo para a seção de provedores?
Michael JDI
Esta é uma classe de teste de unidade completa. plnkr.co/edit/UeCKnJ2sCCpLLQcWqEGX?p=catalogue
Rady
Como você testa o cancelamento da inscrição em ngOnDestroy
shiva
Isso vai quebrar em um caso de uso da vida real porque você não está retornando uma assinatura e não será capaz de usar call .unsubscribe () em ngOnDestroy.
Quovadisqc de
1
data: Observable.of ({yourData: 'yolo'}) funcionaria.
Quovadisqc de
4

Basta adicionar uma simulação do 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
fonte
3

Para algumas pessoas trabalhando em Angular> 5, se Observable.of (); não está funcionando, então eles podem usar apenas of () importando import {of} de 'rxjs';

Shashank Sharma
fonte
1

Encontrou o mesmo problema ao criar suítes de teste para um caminho de roteamento como:

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

No componente, inicializei a propriedade passada como:

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

Ao executar os testes, se você não passar um valor de propriedade em seu ActivatedRoute simulado "useValue", ficará indefinido ao detectar alterações usando "fixture.detectChanges ()". Isso ocorre porque os valores simulados para ActivatedRoute não contêm a propriedade params.property. Em seguida, é necessário que o useValue simulado tenha esses parâmetros para que o aparelho inicialize 'this.property' no componente. Você pode adicioná-lo 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();
          });
      });

Você pode começar a testar como, por exemplo:

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

Agora, digamos que você gostaria de testar um valor de propriedade diferente, então você pode atualizar seu ActivatedRoute simulado 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 isto ajude!

Diego Herrera
fonte
0

Provedor adicionado na classe de teste como:

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