(角单元测试)未捕获错误:未捕获(承诺):错误:无法匹配任何路线。 URL段:“ B1”,(毕竟引发了错误)

时间:2019-04-25 09:21:41

标签: angular unit-testing karma-jasmine angular7

我正在为一个小型角度应用程序编写单元测试用例。它使用路由器,因此我正在使用Angle docs中建议的RouterDirectiveStub。

场景

  • 如果要导航到Bikes/B1,请点击“自行车”组件测试。

  • 在“汽车”组件中,以前的路线在这里造成了麻烦。

    理想情况下,上一个测试不应影响下一个组件。

问题

我应该如何在afterAll()中重置路由器状态?

还是有其他更好的解决方案可以测试?

错误屏幕截图 error_screenshot: Uncaught Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment

错误代码:

START:
  AppComponent
    ✔ should have 2 routerlinks in template
  BikesComponent
    ✔ getBikes api call should be called
    ✔ should set service data to bikes array
    ✔ should hide loader when data is successfully fetched from service
    ✔ should show bike names in template
    ✔ all the bike names should have routerLinks in template
    ✔ should navigate to /bike/id when clicked on that bike name
  CarsComponent
    ✖ getCars api call should be called
    ✔ should set service data to cars array
    ✔ should hide loader when data is successfully fetched from service
    ✔ should show car names in template
    ✔ all the car names should have routerLinks in template
    ✔ should have approprate routerLinks assigned in template
LOG: ''
    ✔ should navigate to /car/id when clicked on that car name
  DetailsComponent
    ✔ #getDetails should be called
    ✔ #getDetails should be called 1 time
    ✔ should assign service data to details variable
    ✔ should disable loader when data is received from service
    ✔ should display details in template
    ✔ should display image in template

Finished in 1.428 secs / 1.228 secs @ 14:33:14 GMT+0530 (India Standard Time)

SUMMARY:
✔ 19 tests completed
✖ 1 test failed

FAILED TESTS:
  CarsComponent
    ✖ getCars api call should be called
      HeadlessChrome 73.0.3683 (Linux 0.0.0)
    Uncaught Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'B1'
    Error: Cannot match any routes. URL Segment: 'B1'
        at ApplyRedirects.push../node_modules/@angular/router/fesm5/router.js.ApplyRedirects.noMatchError (node_modules/@angular/router/fesm5/router.js:2469:1)
        at CatchSubscriber.selector (node_modules/@angular/router/fesm5/router.js:2450:1)
        at CatchSubscriber.push../node_modules/rxjs/_esm5/internal/operators/catchError.js.CatchSubscriber.error (node_modules/rxjs/_esm5/internal/operators/catchError.js:34:1)
        at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._error (node_modules/rxjs/_esm5/internal/Subscriber.js:79:1)
        at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.error (node_modules/rxjs/_esm5/internal/Subscriber.js:59:1)
        at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._error (node_modules/rxjs/_esm5/internal/Subscriber.js:79:1)
        at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.error (node_modules/rxjs/_esm5/internal/Subscriber.js:59:1)
        at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._error (node_modules/rxjs/_esm5/internal/Subscriber.js:79:1)
        at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.error (node_modules/rxjs/_esm5/internal/Subscriber.js:59:1)
        at TapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/tap.js.TapSubscriber._error (node_modules/rxjs/_esm5/internal/operators/tap.js:61:1) thrown

bikes.component.spec.ts

import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import {RouterTestingModule} from '@angular/router/testing';
import { BikesComponent } from './bikes.component';
import { UrlService } from 'src/app/services/url.service';
import { of } from 'rxjs';
import { Router, RouterModule } from '@angular/router';
import { RouterLinkDirectiveStub, click } from 'src/testing';
import { DebugElement } from '@angular/core';
import { AppModule } from 'src/app/app.module';
import { By } from '@angular/platform-browser';
import { IAutomobile } from 'src/app/models/automobile';

const testData: IAutomobile[] = [
    {
    'id': 'B1',
    'name': 'Neiman Marcus Limited Edition Fighter',
    'price': 11,
    'image': 'https://i.imgur.com/93sHCh9.jpg',
    'desc': 'Despite the $11 million price tag and mean looks, the Neiman Marcus Limited Edition Fighter is completely street-legal, managing the road at a 190 mph top speed, the power coming from a 120ci 45-degree air-cooled V-Twin engine complemented by titanium, aluminum, and carbon fiber body parts.',
    'tags': '190 mph | V-Twin engine'
    },
    {
    'id': 'B2',
    'name': 'E90 AJS Porcupine',
    'price': 7,
    'image': 'https://i.imgur.com/44PqbKf.jpg',
    'desc': 'The AJS 500 cc Porcupine was a British racing motorcycle built by Associated Motor Cycles (AMC), which débuted in 1945 with a horizontal-engine designated E90S. A later E95 model was developed with an inclined-engine. AMC produced AJS and Matchless brands at the time.',
    'tags': '550 cc | 135mph'
    },
];
let component: BikesComponent;
let fixture: ComponentFixture<BikesComponent>;
let urlService: jasmine.SpyObj<UrlService>;
let getBikesSpy: jasmine.Spy;

let router: Router;
let routerLinks: RouterLinkDirectiveStub[];
let routerLinksDE: DebugElement[];

describe('BikesComponent', () => {

  beforeEach(async(() => {
    urlService = jasmine.createSpyObj('UrlService', ['getBikes']);
    TestBed.configureTestingModule({
      imports: [ AppModule ]
    })

    // Get rid of app's Router configuration otherwise many failures.
    // Doing so removes Router declarations; add the Router stubs
    .overrideModule(AppModule, {
      remove: {imports: [RouterModule]},
      add: {
        imports: [RouterTestingModule.withRoutes([{path: 'Bikes/B1', component: BikesComponent}])],
        declarations: [ RouterLinkDirectiveStub ],
        providers: [{ provide: UrlService, useValue: urlService }]
      }
    })

    .compileComponents()

    .then(() => {
      fixture = TestBed.createComponent(BikesComponent);
      component = fixture.componentInstance;
      router = TestBed.get(Router);
      urlService = TestBed.get(UrlService);
      getBikesSpy = urlService.getBikes.and.returnValue(of(testData));

      fixture.detectChanges(); // trigger initial changes // must to detectChanges after creating component instance;
    });
  }));

  beforeEach(() => {
    routerLinksDE = fixture.debugElement.queryAll(By.directive(RouterLinkDirectiveStub));
    routerLinks = routerLinksDE.map(de => de.injector.get(RouterLinkDirectiveStub));
  });

  it('getBikes api call should be called', () => {
    expect(getBikesSpy.calls.any()).toBe(true, 'should call getBikes function');
  });

  it('should set service data to bikes array', () => {
    expect(component.bikes).toEqual(testData, 'should set bikes value with service data');
  });

  it('should hide loader when data is successfully fetched from service', () => {
    expect(component.loaded).toBe(true, 'loaded should be true');
  });

  it('should show bike names in template', () => {
    const bikeNames: HTMLCollectionOf<Element> = document.getElementsByClassName('item');
    expect(bikeNames.length).toBe(testData.length, 'should display all the bike names in template, as received by service');
  });

  // // ROUTER LINKS TEST
  it('all the bike names should have routerLinks in template', async(() => {
    expect(routerLinks.length).toBe(testData.length, 'each bike should have routerLink');
  }));

  // this test triggers navigation, which affects CarsComponent tests
  it('should navigate to /bike/id when clicked on that bike name', () => {
    const bikeLinkDe = routerLinksDE[0]; // taking first routerLink for test
    const bikeLink = routerLinks[0];

    fixture.ngZone.run(() => {
      click(bikeLinkDe); // trigger click event on the state name
      fixture.detectChanges(); // update app to detect changes

      expect(bikeLink.navigatedTo).toContain(testData[0].id);
    });
  });

});

cars.component.spec.ts

import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import {RouterTestingModule} from '@angular/router/testing';
import { CarsComponent } from './cars.component';
import { UrlService } from 'src/app/services/url.service';
import { of } from 'rxjs';
import { Router, RouterModule } from '@angular/router';
import { RouterLinkDirectiveStub, click } from 'src/testing';
import { DebugElement } from '@angular/core';
import { AppModule } from 'src/app/app.module';
import { By } from '@angular/platform-browser';
import { IAutomobile } from 'src/app/models/automobile';

const testData: IAutomobile[] = [
    {
        'id': 'C1',
        'name': 'Sweptail Rolls Royce',
        'price': 13,
        'image': 'https://i.imgur.com/LldGUJL.jpg ',
        'desc': 'The Rolls-Royce Sweptail is hand-built, and inspired by coachbuilding of the 1920s and 1930s. The Sweptail was commissioned bespoke in 2013 as a one-off automobile, at the request of a super-yacht and aircraft specialist who had a unique idea in mind. Giles Taylor, director of design at Rolls-Royce Motor Cars described the Sweptail as "the automotive equivalent of Haute couture"',
        'tags': '6.75 L V12  | 453 Hp | 150 MPH'
      },
      {
        'id': 'C2',
        'name': 'Koenigsegg CCXR Trevita',
        'price': 4.8,
        'image': 'https://i.imgur.com/nrK7N62.jpg',
        'desc': 'The Koenigsegg CCX is a mid-engine sports car manufactured by Swedish automotive manufacturer Koenigsegg Automotive AB. The project began with the aim of making a global car, designed and engineered to comply with global safety and environment regulations, particularly to enter the United States car market.',
        'tags': '4.7L V8 | 1004 HP | 254 MPH'
      },
];
let component: CarsComponent;
let fixture: ComponentFixture<CarsComponent>;
let urlService: jasmine.SpyObj<UrlService>;
let getCarsSpy: jasmine.Spy;

let router: Router;
let routerLinks: RouterLinkDirectiveStub[];
let routerLinksDE: DebugElement[];

describe('CarsComponent', () => {

  beforeEach(async(() => {
    urlService = jasmine.createSpyObj('UrlService', ['getCars']);
    TestBed.configureTestingModule({
      imports: [ AppModule ]
    })

    // Get rid of app's Router configuration otherwise many failures.
    // Doing so removes Router declarations; add the Router stubs
    .overrideModule(AppModule, {
      remove: {imports: [RouterModule]},
      add: {
        imports: [RouterTestingModule.withRoutes([{path: 'Cars/C1', component: CarsComponent}])],
        declarations: [ RouterLinkDirectiveStub ],
        providers: [{ provide: UrlService, useValue: urlService }]
      }
    })

    .compileComponents()

    .then(() => {
      fixture = TestBed.createComponent(CarsComponent);
      component = fixture.componentInstance;
      router = TestBed.get(Router);
      urlService = TestBed.get(UrlService);
      getCarsSpy = urlService.getCars.and.returnValue(of(testData));

      fixture.detectChanges(); // trigger initial changes // must to detectChanges after creating component instance;
    });
  }));

  beforeEach(() => {

    routerLinksDE = fixture.debugElement.queryAll(By.directive(RouterLinkDirectiveStub));
    routerLinks = routerLinksDE.map(de => de.injector.get(RouterLinkDirectiveStub));
  });

  it('getCars api call should be called', () => {
    expect(getCarsSpy.calls.any()).toBe(true, 'should call getAllBooks function');
  });

  it('should set service data to cars array', () => {
    expect(component.cars).toEqual(testData, 'should set cars value with service data');
  });

  it('should hide loader when data is successfully fetched from service', () => {
    expect(component.loader).toBe(false, 'loader should be disabled');
  });

  it('should show car names in template', () => {
    const carNames: HTMLCollectionOf<Element> = document.getElementsByClassName('item');
    expect(carNames.length).toBe(testData.length, 'should display all the book names in template, as received by service');
  });

  // ROUTER LINKS TEST
  it('all the car names should have routerLinks in template', async(() => {
    expect(routerLinks.length).toBe(testData.length, 'each car should have routerLink');
  }));

  it('should have approprate routerLinks assigned in template', async(() => {
    expect(routerLinks[0].linkParams).toContain(`${testData[0].id}`, 'the first routerLink sould have 1st carId');
    expect(routerLinks[1].linkParams).toContain(`${testData[1].id}`, 'the second routerLink sould have 2nd carId');
  }));

  // this test triggers navigation, which affects DetailsComponent tests
  it('should navigate to /car/id when clicked on that car name', () => {
    const carLinkDe = routerLinksDE[0]; // taking first routerLink for test
    const carLink = routerLinks[0];

    fixture.ngZone.run(() => {
      click(carLinkDe); // trigger click event on the state name
      fixture.detectChanges(); // update app to detect changes

      expect(carLink.navigatedTo).toContain(testData[0].id);
    });
  });

});

0 个答案:

没有答案