【问题标题】:Angular 2 how do I test a component that uses routerAngular 2如何测试使用路由器的组件
【发布时间】:2017-06-09 04:57:03
【问题描述】:

我正在尝试为调用 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 未处理的承诺拒绝:无法匹配任何路由。 URL 段:“仪表板”

所以我从那里开始考虑使用withRoutes 添加路线。我不喜欢我需要包含DashboardComponent,因为似乎应该有一些可用的模拟/空白组件,特别是因为我不想实际导航和加载另一条路线,但我找不到任何东西像这样:

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

但是这只是给我一个新的错误:

Component DashboardComponent 不是任何 NgModule 的一部分,或者该模块尚未导入到您的模块中。

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

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

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

未处理的承诺拒绝:找不到加载“仪表板组件”的主要出口

在这一点上,似乎必须有一种更简单的方法来做到这一点,因为这是一种非常常见的情况,但我已经尝试了其他人说他们使用过的所有方法,而且一切都让这个兔子洞更进一步。

【问题讨论】:

    标签: angular karma-jasmine


    【解决方案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']);
        });
      });
    });
    

    【讨论】:

    • 您是如何访问component.router 的?如果不公开我的组件中路由器的 DI,我将无法访问它。否则,感谢您指出 RouterTestModule :)
    • 默认情况下,CLI 将组件类型为当前组件。即let component: LoginComponent;。我不确定他们为什么这样做,因为它会导致问题并阻止访问私有变量和方法。将其更改为键入 any 可解决此问题。 let component: any; 这很痛苦,因为您无法更改模板并且必须在每个规范中进行此更改。
    • 谢谢。我在您的解决方案上使用了一个细微的变化,唯一的区别是传递给注射器的东西。我将它作为解决方案发布以防万一,这只是因为 cmets 中的代码令人困惑。使用它无需更改您的规范以使用let component: any;
    【解决方案2】:

    我找到了一个与@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】:
      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或者延迟拨打ticktick(someMilliseconds)

      【讨论】:

      • 我已经尝试过这个,因为我在许多其他测试中使用了 mockRouter 方法。似乎因为我在这个组件中使用了activatedRoute,它使事情复杂化了。如果我切换到一个 mockRouter,我会收到一个错误,指出没有 ActivatedRoute 的提供者,我会模拟它,然后我会收到一个错误,指出没有 LocationStrategy 的提供者,我再次陷入我应该的兔子洞不需要下去。
      • 你可能是机器人在嘲笑它。 stackoverflow.com/a/42022504/2587435
      • 这与我所做的略有不同,但会导致相同的 LocationStrategy 错误。
      • 如果您正确配置所有内容,则没有理由出现该错误。你正在使用模拟。模拟不需要 LocationStrategy。不知何故,您仍在尝试使用真实的
      • 我同意,但应用程序中的某些内容似乎需要 LocationStrategy,即使路由器和 activateRoute 被模拟。这可能是开发人员推荐使用包含 LocationStrategy 的 RouterTestingModule 的原因
      【解决方案4】:

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

      【讨论】:

      • 你根本没看代码吗?我一直在使用它。
      • 在否决问题并提供 OP 已经在使用的相同解决方案之前,请实际阅读多于标题的内容。
      • 此外,您为指南提供的链接甚至没有提供有关 RouterTestingModule 的任何信息,而是建议只为有其自身问题的路由器创建一个存根。
      猜你喜欢
      • 1970-01-01
      • 2017-02-27
      • 2019-07-15
      • 1970-01-01
      • 2021-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多