Angular2:如何测试有时会返回的组件函数,有时会路由? [英] Angular2: How to test a component function that sometimes returns, sometimes routes?

查看:128
本文介绍了Angular2:如何测试有时会返回的组件函数,有时会路由?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我还在开发基于Angular2 Heroes教程的应用程序。此时,我有一个用户进行编辑的组件,单击保存,并且在成功时,用户将被激活到父页面(路径../)。如果保存时出错,则路由不会发生,页面会显示错误信息。

I'm still developing an app based on the Angular2 Heroes tutorial. At this point I have a component where the user makes an edit, clicks on Save and on success the user is spirited to the parent page (route "../"). If there is an error in saving then the routing doesn't occur, and the page displays the error information.

组件的保存功能中出现了精神错误:

The spiriting occurs in the component's save function:

private gotoParent(): void {
  this.router.navigate(['../'], { relativeTo: this.route });
}

public save(): void {
  this.error = null;
  let that = this;

  this.orgService
      .save(that.org)
      .subscribe(
          (org: Org): void => {
            that.org = org; 
            that.savedOrg = new Org(that.org);
            that.gotoParent();
        },
        error => this.error = error
      );

}

我到目前为止的测试是:

The test I have so far is:

routeStub = { data: Observable.of( { org: org1 } ), snapshot: {} };

TestBed.configureTestingModule({
    imports: [ FormsModule, RouterTestingModule ],
    providers : [
        { provide: DialogService, useClass: MockDialogService },
        { provide: GlobalsService, useClass: MockGlobalsService },
        { provide: OrgService, useClass: MockOrgService },
        { provide: ActivatedRoute, useValue: routeStub }          
    ],
    declarations: [ OrgDetailComponent ],
  })
  .compileComponents();
}));

...

it('responds to the Save click by saving the Org and refilling the component', async(() => {
  fixture.detectChanges();
  fixture.whenStable().then(() => {
    comp = fixture.componentInstance;
    comp.org = new Org(org1);
    comp.org.id = 2;
    comp.org.name = 'Another Org';

    let elButton = fixture.debugElement.query(By.css('#save'));
    elButton.nativeElement.click();

    fixture.detectChanges();
    fixture.whenStable().then(() => {
      expect(comp.error).toBeNull();
      expect(comp.savedOrg.id).toEqual(2);
      expect(comp.savedOrg.name).toEqual('Another Org');
      expect(routeStub).toHaveBeenCalledWith(['../']);
    });
  });    

}));

当调用expect(routeStub)时,我得到错误:期望一个间谍,但得到了对象。 ..。

When the expect(routeStub) is called I get "Error: expected a spy, but got Object ...".

大多数关于测试路由的教程都设置了路由表并对其进行了测试。我不确定我是否需要路由类(替换ActivatedRoute?)。

Most tutorials regarding testing routing set up a routing table and test that. I'm not sure if I need a route class (replacing ActivatedRoute?) or not.

谢谢,

杰罗姆。

3/25更新

snorkpete的回答,在peeskillet的其他主题中,并没有解决我的问题。我认为这是因为我的代码中有两个不同的东西,我在这里只共享了一个。

The answer by snorkpete, and in other threads by peeskillet, aren't solving my issues. I think that this is because I've two different things going on in my code, and I've shared only one here.

我的组件有一个ngOnInit()依赖于解析器将数据传递到ngOnInit()中的subscribe()。在我的测试中,这是由(重命名的)activatedRouteStub实例提供的:

My component has an ngOnInit() that relies on a resolver to deliver data to the subscribe() within the ngOnInit(). In my tests this is provided by the (renamed) activatedRouteStub instance:

activatedRouteStub = { data: Observable.of( { org: org1 } ) }

在测试ngOnInit()时,我得到了提供的Org对象。

In testing the ngOnInit() I get the provided Org object just fine.

现在我还需要处理一个Save按钮,这也会导致浏览器显示父页面。组件调用:

Now I need to also process a Save button, which also causes the browser to display the parent page. The component calls:

this.router.navigate(['../'], {relativeTo: this.route});

如果我删除activatedRouteStub,将其替换为routerStub,一切都会中断。

If I remove the activatedRouteStub, replacing it with a routerStub, everything breaks.

如果我同时使用activatedRouteStub和routerStub,则调用

If I use both activatedRouteStub and routerStub, the call

expect(routerStub.navigate).toHaveBeenCalled()

失败,抱怨期待间谍并获得一个对象。

fails, complaining about expecting a spy and getting an Object.

如果我添加导航:jasmineCreateSpy('navigate')到activatedRouteStub并在activatedRouteStub.navigate()上执行expect()我被告知那个没有被导航。

If I add the navigate: jasmineCreateSpy('navigate') to the activatedRouteStub and do the expect() on activatedRouteStub.navigate() I'm told that that wasn't navigated against.

我很困惑。

杰罗姆。

解决方案在3/25,15:00 CDT

感谢先前的帮助通过peeskillet和来自snorkpete的直接帮助我已经回答了我的问题。

Thanks to prior help by peeskillet and immediate help from snorkpete I've an answer to my issues.

我碰巧需要ActivatedRoute和路由器。更重要的是,当我调用toHaveBeenCalledWith()时,我需要提供所有提供this.router.navigate()调用的内容。对我来说是一个DUH的观察,但没有意识到这浪费了我很多时间。

I happen to need both an ActivatedRoute and a router. What is more, when I call toHaveBeenCalledWith() I need to provide ALL of what the this.router.navigate() call was provided. A "DUH" obvervation on my part, but not realizing it wasted tons of my time.

要在一个地方获得完整的解决方案,这里是我的组件及其测试规范的相关代码。

To get a complete solution into one place, here is pertinent code for my component and its test spec.

对于组件:

public ngOnInit(): void {
  this.error = null;
  this.stateOptions = this.globalsService.getStateOptions();
  let that = this;

  this.route.data
    .subscribe((data: { org: Org }) => {
      that.org = data.org;
      that.savedOrg = new Org(that.org);
    });
}

private gotoParent(): void {
  this.router.navigate(['../'], { relativeTo: this.route });
}

public save(): void {
  this.error = null;
  let that = this;

  this.orgService
      .save(that.org)
      .subscribe(
          (org: Org): void => {
            that.org = org; 
            that.savedOrg = new Org(that.org);
            that.gotoParent();
        },
        error => this.error = error
      );

}

注意goToParent()使用路由字符串和relativeTo :参数。

Note that goToParent() uses a route string and a relativeTo: parameter.

在测试中:

@Injectable()
export class MockActivatedRoute {
  constructor() { }

  data: Observable<Org> = null;
}

@Injectable()
export class MockRouter {
  constructor() { }

  navigate: any = () => {};

  snapshot: any = {};
}

describe("...", () => {

  ...

  let router: Router = null;
  let activatedRoute: ActivatedRoute = null;

  beforeEach(async(() => {

    TestBed.configureTestingModule({
      imports: [ FormsModule, RouterTestingModule ],
      providers : [
        { provide: DialogService, useClass: MockDialogService },  // don't worry about these three in this example...
        { provide: GlobalsService, useClass: MockGlobalsService },
        { provide: OrgService, useClass: MockOrgService },
        { provide: Router, useClass: MockRouter },
        { provide: ActivatedRoute, useClass: MockActivatedRoute }          
      ],
      declarations: [ OrgDetailComponent ],
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(OrgDetailComponent);

    dialogService = fixture.debugElement.injector.get(DialogService);
    globalsService = fixture.debugElement.injector.get(GlobalsService);
    orgService = fixture.debugElement.injector.get(OrgService);
    router = fixture.debugElement.injector.get(Router);
    activatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
  });

  it('responds to the Save click by saving the Org and refilling the component', async(() => {
    activatedRoute.data = Observable.of( { org: org1 } ); // The org1 is an instance of Org.
    let spy = spyOn(router, 'navigate');
    fixture.detectChanges();
    fixture.whenStable().then(() => {
      comp = fixture.componentInstance;
      comp.org = new Org(org1);
      comp.org.id = 2;
      comp.org.name = 'Another Org';

      let elButton = fixture.debugElement.query(By.css('#save'));
      elButton.triggerEventHandler('click', null);

      fixture.detectChanges();
      fixture.whenStable().then(() => {
        expect(comp.error).toBeNull();
        expect(comp.savedOrg.id).toEqual(2);
        expect(comp.savedOrg.name).toEqual('Another Org');
        expect(router.navigate).toHaveBeenCalled();
        expect(router.navigate).toHaveBeenCalledWith(['../'], { relativeTo: activatedRoute });
      });
    });    

  }));

});


推荐答案

在你的例子中,你是错误的东西。接近您尝试做的最简单的方法是意识到您的组件依赖于路由器服务。 (记住,它调用 router.navigate )。这是您想要用mock / stub对象替换的复杂依赖项。

In your example, you're stubbing the wrong thing. The easiest way to approach what you're trying to do is to realise that your component has a dependency on the router service. (remember, it calls router.navigate). This is the 'complicated dependency' that you want to replace with your mock/stub object.

因此,您应该更改测试模块中的提供程序列表以提供存根for Router返回具有导航方法的虚拟对象。然后,您可以确认在您希望调用存根中的导航方法是否被调用。

So you should change your list of providers in your test module to provide a stub for Router that returns a dummy object that has a navigate method. Then you can confirm if that navigate method in your stub is called when you expect it to be called.

 providers : [
        { provide: DialogService, useClass: MockDialogService },
        { provide: GlobalsService, useClass: MockGlobalsService },
        { provide: OrgService, useClass: MockOrgService },
        //{ provide: ActivatedRoute, useValue: routeStub }  <-- remove this   
        { provide: Router, useValue: routerStub }       <-- add this  
    ],

如前所述,您的路由器存根是一个虚拟对象,上面有一个导航方法。你必须spyOn那个方法。

As stated previously, your router stub is a dummy object with a single 'navigate' method on it. You'll have to spyOn that method.

let fakeRouter = TestBed.get(Router);  // you must retrieve your router fake through dependency injection
spyOn(fakeRouter, 'navigate');

然后在你的测试中,

 expect(fakeRouter.navigate).toHaveBeenCalledWith(['../']);

请注意,您正在监视和测试的路由器对象不能是路由器存根您es6导入到您的测试文件中。你必须确保通过依赖注入检索你的fakeRouter。

Note that the 'router' object that you're spying and testing against can't be the routerStub that you es6 imported into your test file. You have to ensure that you retrieve your fakeRouter through dependency injection.

编辑

额外的信息是有用的 - 你可以使用routeStub来存储ActivatedRoute - 你可能已经意识到,routeStub用作从解析器获取数据的替代品。这部分工作正常。但是,既然您还要确认使用您期望的方式调用 router.navigate 方法,那么您还必须存根。

The extra information is helpful - you are going okay with stubbing the ActivatedRoute with your routeStub - as you may have realised, that routeStub is used as a replacement for getting the data from the resolver. So that part works fine. But since you also want to confirm that the router.navigate method is called with what you expect, then you also have to stub that.

因此,您的测试模块的提供者列表应该具有:

So, your test module's provider list should have:

providers : [
  { provide: DialogService, useClass: MockDialogService },
  { provide: GlobalsService, useClass: MockGlobalsService },
  { provide: OrgService, useClass: MockOrgService },
  { provide: ActivatedRoute, useValue: routeStub }, //<-- to simulate the resolver passing data to your component          
  { provide: Router, useValue: routerStub },  //<-- dummy object with navigate method that you spy on to ensure you navigate when you expect to 
],

如前所述,routerStub是一个带有单个导航的简单对象你要监视的方法,看它是否被正确调用。

As mentioned before, the routerStub is a simple object with a single navigate method that you are going to spy on to see if it gets called correctly.

所以,在你的测试中,

it('responds to the Save click by saving the Org and refilling the component', async(() => {

  // get an instance of your router from your TestBed.
  // but because of how your providers are configured,
  // when you ask for an instance of Router, TestBed will instead
  // return an instance of your routerStub.
  // You MUST get your routerStub through dependency injection -
  // either using TestBed.get or the inject function or some other means
  let fakeRouter = TestBed.get(Router);


  // This is jasmine at work now.
  // Later on, we want to confirm that the navigate method on
  // our fakeRouter is called, so we tell jasmine to monitor that method
  // Jasmine won't allow that spyOn call to work unless 
  // fakeRouter actually has a navigate method - hence the reason
  // our routerStub needed to implement one
  spyOn(fakeRouter,'navigate');

  fixture.detectChanges();
  fixture.whenStable().then(() => {
    comp = fixture.componentInstance;
    comp.org = new Org(org1);
    comp.org.id = 2;
    comp.org.name = 'Another Org';

    let elButton = fixture.debugElement.query(By.css('#save'));
    elButton.nativeElement.click();

    fixture.detectChanges();
    fixture.whenStable().then(() => {
      expect(comp.error).toBeNull();
      expect(comp.savedOrg.id).toEqual(2);
      expect(comp.savedOrg.name).toEqual('Another Org');

      // we set up our spy on our navigate method above.
      // now, we check that the method in question has actually been called.
      // note that i'm checking the method itself -
      // in spying, jasmine replaces that 'navigate' method 
      // with something else that it can later call assertions with
      // Hence, we check against that property explicitly
      expect(fakeRouter.navigate).toHaveBeenCalledWith(['../']);
    });
  });    

}));

这篇关于Angular2:如何测试有时会返回的组件函数,有时会路由?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆