关闭

如何对 Service 单元测试 ?

发表于:2018-1-29 10:46

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:点灯坊    来源:个人博客

  AppComponent
  app.component.spec.ts
  import { async, ComponentFixture, TestBed } from '@angular/core/testing';
  import { AppComponent } from './app.component';
  import { FormsModule } from '@angular/forms';
  import { HttpClientTestingModule } from '@angular/common/http/testing';
  import { PostService } from './service/post.service';
  import { PostInterfaceToken } from './interface/injection.token';
  import { DebugElement } from '@angular/core';
  import { Observable } from 'rxjs/Observable';
  import 'rxjs/add/observable/of';
  import { PostInterface } from './interface/post.interface';
  describe('AppComponent', () => {
    let fixture: ComponentFixture<AppComponent>;
    let appComponent: AppComponent;
    let debugElement: DebugElement;
    let htmlElement: HTMLElement;
    let postService: PostInterface;
    beforeEach(async(() => {
      TestBed.configureTestingModule({
        declarations: [
          AppComponent
        ],
        imports: [
          FormsModule,
          HttpClientTestingModule
        ],
        providers: [
          {provide: PostInterfaceToken, useClass: PostService}
        ]
      }).compileComponents();
      fixture = TestBed.createComponent(AppComponent);
      appComponent = fixture.componentInstance;
      debugElement = fixture.debugElement;
      htmlElement = debugElement.nativeElement;
      fixture.detectChanges();
      postService = TestBed.get(PostInterfaceToken, PostService);
    }));
    
    it('should create the app', async(() => {
      expect(appComponent).toBeTruthy();
    }));
    it(`should have as title 'app'`, async(() => {
      expect(appComponent.title).toEqual('app');
    }));
    it('should render title in a h1 tag', async(() => {
      expect(htmlElement.querySelector('h1').textContent).toContain('Welcome to app!');
    }));
    it(`should list posts`, () => {
      const expected$ = Observable.of([
        {
          id: 1,
          title: 'Design Pattern',
          author: 'Dr. Eric Gamma'
        }
      ]);
      /** arrange */
      spyOn(postService, 'listPosts$').and.returnValue(expected$);
      /** act */
      appComponent.onListPostsClick();
      /** assert */
      expect(appComponent.posts$).toEqual(expected$);
    });
    it(`should add post`, () => {
      const expected$ = Observable.of(
        {
          id: 1,
          title: 'Design Pattern',
          author: 'Dr. Eric Gamma'
        }
      );
      /** arrange */
      const spy = spyOn(postService, 'addPost').and.returnValue(expected$);
      /** act */
      appComponent.onAddPostClick();
      /** assert */
      expect(spy).toHaveBeenCalled();
    });
  });
  20 行
  TestBed.configureTestingModule({
    declarations: [
      AppComponent
    ],
    imports: [
      FormsModule,
      HttpClientTestingModule
    ],
    providers: [
      {provide: PostInterfaceToken, useClass: PostService}
    ]
  }).compileComponents();
  跑 component 单元测试时,一样没有使用AppModule?的设定,因为我们可能在测试时使用其他替代 module,也可能自己实作 fake 另外 DI。
  因此一样使用TestBed.configureTestingModule(),让我们另外设定跑测试时的?imports?与?providers?部分。
  24 行
  imports: [
    FormsModule,
    HttpClientTestingModule
  ],
  因为在 component 使用了 two-way binding,因此要加上FormsModule。
  Q : 为什么要 import?HttpClientTestingModule?呢 ?
  AppComponent?依赖的是?PostService,看起来与?HttpClient?无关,应该不需要注入HttpClientTestingModule。
  但其实 DI 并不是这样运作,虽然AppComponent?只用到了?PostService,但 DI 会将?PostService?下所有的 dependency 都一起注入,所以也要 import?HttpClientTestingModule。
  28 行
  providers: [
    {provide: PostInterfaceToken, useClass: PostService}
  ]
  由于我们要测试的就是PostService,因此PostService?也必须由 DI 帮我们建立。
  但因爲?PostService?是基于PostInterface?建立,因此必须透过PostInterfaceToken?mapping 到?PostService。
  39 行
  1 postService = TestBed.get(PostInterfaceToken, PostService);
  由providers设定好 interface 与 class 的 mapping 关系后,我们必须透过 DI 建立postService?与。
  53 行
  it(`should list posts`, () => {
    const expected$ = Observable.of([
      {
        id: 1,
        title: 'Design Pattern',
        author: 'Dr. Eric Gamma'
      }
    ]);
    /** arrange */
    spyOn(postService, 'listPosts$').and.returnValue(expected$);
    /** act */
    appComponent.onListPostsClick();
    /** assert */
    expect(appComponent.posts$).toEqual(expected$);
  });
  55 行
  const expected$ = Observable.of([
    {
      id: 1,
      title: 'Design Pattern',
      author: 'Dr. Eric Gamma'
    }
  ]);
  透过Observable.of()?将?Post[]?转成?Observable<Post[]>。
  63 行
  /** arrange */
  spyOn(postService, 'listPosts$').and.returnValue(expected$);
  由于我们要隔离PostService,因此使用spyOn()?对?PostService?的?listPost$()?加以 mock,并设定其假的回传值。
  65 行
  /** act */
  appComponent.onListPostsClick();
  实际测试onListPostClick()。
  67 行
  /** assert */
  expect(appComponent.posts$).toEqual(expected$);
  测试AppComponent.post$?是否如预期。
  Q : 我们在onListPostsClick()到底测试了什么 ?
  ●若 component 的 return 与 expected 不同,可能是 component 的逻辑错误
  70 行
  it(`should add post`, () => {
    const expected$ = Observable.of(
      {
        id: 1,
        title: 'Design Pattern',
        author: 'Dr. Eric Gamma'
      }
    );
    /** arrange */
    const spy = spyOn(postService, 'addPost').and.returnValue(expected$);
    /** act */
    appComponent.onAddPostClick();
    /** assert */
    expect(spy).toHaveBeenCalled();
  });
  72 行
  const expected$ = Observable.of(
    {
      id: 1,
      title: 'Design Pattern',
      author: 'Dr. Eric Gamma'
    }
  );
  透过Observable.of()?将Post转成Observable<Post>。
  80 行
  /** arrange */
  const spy = spyOn(postService, 'addPost').and.returnValue(expected$);
  由于我们要隔离PostService,因此使用spyOn()对PostService的addPost加以 mock,并设定其假的回传值。
  82 行
  /** act */
  appComponent.onAddPostClick();
  实际测试onAddPostClick()。
  84 行
  /** assert */
  expect(spy).toHaveBeenCalled();
  由于onAddPostClick()回传值为void,且PostService.addPost()已经有单元测试保护,因此只要测试PostService.addPost()曾经被呼叫过即可。
  使用 Wallaby.js 通过所有 component 单元测试。
  Conclusion
  当 component 使用了 service,若要单元测试就牵涉到 DI 与spyOn()
  Service 单元测试可透过HttpTestingController加以隔离HttpClient
  Component 单元测试可透过spyOn()加以隔离 service


上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号