【问题标题】:Testing a method that is subscribed to an observable - Angular 2测试订阅可观察的方法 - Angular 2
【发布时间】:2016-06-10 02:17:23
【问题描述】:

我想测试 Angular 2 组件内部的一个方法,该组件订阅了一个从服务中的方法返回的 observable。下面是服务方法的代码总结:

public create(user: User): Observable<any> {
  return this.http.post(this._api.create,
    JSON.stringify(user), {
      headers: this.apiConfig.getApiHeaders()
    }).map((res: Response) => res.json());
  }

对这个方法进行单元测试很容易,因为它返回一个 observable,所以我可以订阅它。但是我想测试已经订阅这个的组件中的方法:

public onSubmit(user: User): void {
  this._authentication.create(user).subscribe((token) => {
    localStorage.setItem('token', token);
    this.router.navigate(['/Home']);
  });
}

到目前为止,这是我的规范,但是当我尝试监视 localStorage.setItem 时,它返回为没有被调用。我的理解是它可能在实际调用之前检查它是否被调用。

it('Should login a user and on success store a token in localStorage',
  injectAsync([TestComponentBuilder], (tcb) => {
    return tcb.createAsync(Login).then((fixture) => {
      let instance = fixture.debugElement.componentInstance;
      localStorage.clear();
      spyOn(localStorage, 'setItem');
      instance.onSubmit({userId: 'some@email.com', password: 'password', siteName: 'sample'});
      expect(localStorage.setItem).toHaveBeenCalled();
    });
  })
);

我想知道是否需要模拟 this._authentication.create 方法以返回一个带有模拟响应的新 observable?

经过更多研究,一些文章表明我确实需要模拟服务并返回一个同步运行的 Observable.of() 来解决问题,我复制下面的代码。然而这仍然不起作用,我大部分时间都在做这个,我不觉得这应该那么难,任何帮助表示赞赏。

class MockAuthentication extends Authentication {
  public create(user: Object): Observable<any> {
    return Observable.of({'test': 'test'});
  }
}

【问题讨论】:

    标签: jasmine angular rxjs


    【解决方案1】:

    好吧,我花了一天的大部分时间,但我终于破解了它。而不是使用 injectAsync 和 TestComponentBuilder 来设置规范,我只需要像你做服务一样使用注入和注入组件。这似乎很好,因为我不需要在视图中测试任何东西,比如事件。

    这是最终的规范:

    it('Should set token in localStorage, set the new user, 
    and navigate to home page on succesful login', 
      inject([Login], (login) => {
        login.router.config([ { path: '/', name: 'Home', component: Home }]);
        spyOn(localStorage, 'setItem');
        spyOn(login._currentUser, 'set');
        spyOn(login.router, 'navigate');
        login.onSubmit({ userId: 'some@email.com', password: 'password', siteName: 'sample' });
        expect(localStorage.setItem).toHaveBeenCalledWith('token', 'newToken');
        expect(login._currentUser.set).toHaveBeenCalledWith({ 'test': 'one' });
        expect(login.router.navigate).toHaveBeenCalledWith(['/Home']);
    }));
    

    希望这可能对将来的某人有所帮助。

    【讨论】:

      【解决方案2】:

      我猜你想向你的组件注入一个模拟 Router 实例,然后在模拟 Router 上调用 navigate(['/Home']) 之后,检查是否调用了 localStorage.setItem(...)

      【讨论】:

      • 感觉检查会在他们因为订阅而被调用之前发生,但我是 Observables 的新手。此外,我们过去只在 Angular 1 中使用 spyOn(localStorage, 'setItem') 但我很难弄清楚如何在 Angular 2 中做同样的事情。
      • 我的错,看起来你仍然使用 spyOn,我的问题是我无法弄清楚你需要从哪里导入 'spyOn' 但似乎你已经有了它。但似乎我关于之前发生的检查的说法仍然成立。我已经更新了我的问题以包含规范。
      【解决方案3】:

      See my gist here.

      基本上你可以在这里做几件事。首先,使用您想要的令牌(或其他响应)的简单可观察响应来存根您的 http 调用(我猜测来自服务)。

      service.stub.ts

      export class MyStub {
        public create(user: User): Observable<User> {
          return Observable.of('insert test token here');
        }
      // other stubbed methods ...
      }
      

      然后在你的测试中:

      myComp.spec.ts

      let comp: MyComponent;
      let fixture: ComponentFixture<MyComponent>;
      let sst: ServiceStub;
      describe('MyComponent', () => {
        beforeEach(async(() => {
          TestBed.configureTestingModule({
            declarations: [MyComponent],
            schemas: [NO_ERRORS_SCHEMA]
          }).overrideComponent(OnboardFacilityNewComponent, {
            set: {
              providers: [
                { provide: MyService, useClass: ServiceStub },
              ]
            }
          })
          .compileComponents()
            .then(() => {
              fixture = TestBed.createComponent(MyComponent);
              comp = fixture.componentInstance;
              st = fixture.debugElement.injector.get(MyService);
            });
        }));
        it('should submit new onboardFacility', fakeAsync(() => {
          const sst = spyOn(sst, 'create').and.returnValue(
            Observable.of('some token here')
          );
          comp.onSubmit(testUser);
          fixture.detectChanges();
          expect(comp.token).toEqual('some token here');
          expect(spy.calls.any()).toEqual(true);
        }));
      });
      

      在这里,您可以简单地将实际数据替换为测试数据来测试您的测试行为,而不是测试您的测试平台、您的服务、localStorage 等。显然我在这里编写的测试假设您将存储从您的服务返回的令牌在您的组件中,而不是 localStorage (though there is a way to do that),但我只是简单地展示概念而不是您的具体用例。

      在您的用例中,您还需要存根路由器,您可以学习如何操作here.

      【讨论】:

      • 这毫无意义.. 你为什么在服务模拟中调用 spyOn?如果它被模拟了,它也返回了一个模拟值,你不需要使用 returnValue Jasmine 函数返回另一个模拟值。
      • @EliasGarcia 因为您正在确定函数逻辑是否调用依赖项。间谍是可选的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-14
      • 2018-01-19
      • 2016-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-23
      相关资源
      最近更新 更多