Angular 2如何测试使用路由器的组件

时间:2017-05-10 23:00:47

标签: angular karma-jasmine

我正在尝试为一个调用router.navigate()的组件编写一些测试,并且我在声明路由时遇到错误。我已经阅读了很多内容并尝试了所有这些内容,但它们都会导致一些错误或其他错误。我使用的是Angular 4.0.0。

组件:

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
  constructor(
    private activatedRoute: ActivatedRoute,
    private authService: AuthService,
    private formBuilder: FormBuilder,
    private jwtService: JwtService,
    private router: Router,
    private storageService: StorageService
  ) { ... }

  ngOnInit() {
  }

  private login(formData: any): void {
    const credentials: any = {
      email: formData.controls.email.value,
      password: formData.controls.password.value
    };
    this.authService.login(credentials).subscribe(res => {
      this.activatedRoute.params.subscribe(params => {
        if (params.returnUrl) {
          this.router.navigate([params.returnUrl]);
        } else {
          this.router.navigate(['/dashboard']);
        }
      });
    }, error => { ... });
  }
}

测试

describe('LoginComponent', () => {
  let component: any;
  let fixture: ComponentFixture<LoginComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ LoginComponent ],
      imports: [
        SharedModule,
        RouterTestingModule
      ],
      providers: [{
        provide: AuthService,
        useClass: MockAuthService
      }, {
        provide: JwtService,
        useClass: MockJwtService
      }, {
        provide: StorageService,
        useClass: MockStorageService
      }],
      schemas: [ NO_ERRORS_SCHEMA ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  describe('login', () => {
    it('should call router.navigate with the dashboard route if the login is successful', () => {
      spyOn(component.router, 'navigate');
      component.authService.login.and.returnValue(Observable.of({ access_token: 'fake_token' }));
      component.login(component.loginForm);
      expect(component.router.navigate).toHaveBeenCalledWith(['/dashboard']);
    });
  });
});

这一切都给了我以下错误:

  

zone.js:569未处理承诺拒绝:无法匹配任何路线。网址细分:'信息中心'

所以我从那里开始用withRoutes添加路线。我不喜欢我需要包含DashboardComponent,因为看起来应该有一些模拟/空白组件可用于此,特别是因为我不想实际导航和加载另一条路线但我不能找到类似的东西:

TestBed.configureTestingModule({
  declarations: [ LoginComponent ],
  imports: [
    SharedModule,
    RouterTestingModule.withRoutes([{
      path: 'dashboard',
      component: DashboardComponent
    }])
  ],
  ...
})
.compileComponents();

然而,这只是给我一个新错误:

  

组件DashboardComponent不是任何NgModule的一部分,或者模块尚未导入模块。

所以我想也许我需要声明DashboardComponent,所以我把它添加到声明数组中:

TestBed.configureTestingModule({
  declarations: [ LoginComponent, DashboardComponent ],
  ..
})
.compileComponents();

然而,这只会导致另一个错误:

  

未处理的承诺拒绝:找不到加载'DashboardComponent'的主要插座

此时似乎必须有一种更简单的方法来实现这一点,因为这是一种非常常见的情况,但我已经尝试过其他人所说的所有内容,所有这些都只是导致这个兔子洞的进一步发展。

4 个答案:

答案 0 :(得分:1)

spyOn(component.router, 'navigate');

你不能这样做,因为真正的Router需要配置一些其他的东西才能工作。您应该创建一个不需要任何内容​​的Router模拟。

providers: [
  {
    provide: Router,
    useValue: { navigate: jasmine.createSpy('navigate') }
  }
]

现在你可以做到

expect(component.router.navigate).toHaveBeenCalledWith(['/dashboard']);

但另一个问题是你可以同步调用它。在login方法的旁边,您有两种不同级别的异步调用

this.authService.login(credentials).subscribe(res => {
  this.activatedRoute.params.subscribe(params => {

所以你需要等待这些完成。您可以使用fakeAsynctick

it('..', fakeAsync(() => {
  ..login()
  tick()
  expect(component.router.navigate).toHaveBeenCalledWith(['/dashboard']);
}))

我不太确定在这种情况下它会如何工作,因为你有两级异步调用。我不确定tick()是否会等待一转,或者它是否也会捕获第二个异步调用。如果它不起作用,您可以尝试再次致电tick或致电延迟tick(someMilliseconds)

答案 1 :(得分:1)

解决方案结果非常简单......

只需添加RouterTestingModule,我只需要在所有测试中监视router.navigate,以防止他们尝试实际导航到另一条路径。

describe('LoginComponent', () => {
  let component: any;
  let fixture: ComponentFixture<LoginComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ LoginComponent ],
      imports: [
        SharedModule,
        RouterTestingModule   // This provides the mock router, location and routerLink
      ],
      providers: [{
        provide: AuthService,
        useClass: MockAuthService
      }, {
        provide: JwtService,
        useClass: MockJwtService
      }, {
        provide: StorageService,
        useClass: MockStorageService
      }],
      schemas: [ NO_ERRORS_SCHEMA ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    spyOn(component.router, 'navigate');    // This prevents every test from calling the real router.navigate which means I don't need to add routes to RouterTestingModule
  });

  describe('login', () => {
    it('should call router.navigate with the dashboard route if the login is successful', () => {
      spyOn(component.router, 'navigate');
      component.authService.login.and.returnValue(Observable.of({ access_token: 'fake_token' }));
      component.login(component.loginForm);
      expect(component.router.navigate).toHaveBeenCalledWith(['/dashboard']);
    });
  });
});

答案 2 :(得分:1)

我找到了一个稍微不同的解决方案,然后@efarley,它接受了他的答案,并改变了注射器的提供方式。以为我会添加它以防万一它可以帮助任何人。我使用了Angular CLI,因此使用默认的测试蓝图设置了规范,为了简洁起见,我删除了一些内容。

// NOTE: Other required test imports removed for brevity so this snippet
// only represents the minimum viable code required to test a route
import { RouterTestingModule } from '@angular/router/testing';
import { Router } from '@angular/router';

describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let de: DebugElement;
  let el: HTMLElement;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        LoginComponent
      ],
      providers: []
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    de = fixture.debugElement;
    fixture.detectChanges();
  });

  it('form submission login and navigating to the dashboard', () => {
    el = de.nativeElement.querySelector('button[type="submit"]');

    // **ONLY** difference between the solutions is what is passed to the injector
    spyOn(de.injector.get(Router), 'navigate');
    el.click();

    expect(de.injector.get(Router).navigate)
      .toHaveBeenCalledWith(['dashboard']);
  });

答案 3 :(得分:-1)

您可以使用RouterTestingModule。测试指南包含信息here