【问题标题】:Angular 2 Unit Test (TestBed) service with .asObservable()带有 .asObservable() 的 Angular 2 单元测试 (TestBed) 服务
【发布时间】:2017-11-27 11:13:57
【问题描述】:

我有一个组件调用服务来查看是否已从另一个组件宣布订阅。

组件:

this.activateProcessReadySubscription =  this.returnService.processReadySubscriptionAnnouced$.subscribe(
            itemsInCart => {
                this.itemsInCart = itemsInCart;
            });

当我尝试对此进行测试时,出现错误:

TypeError: 无法读取未定义的属性“订阅”

规格

it('should call constructor', fakeAsync(() => {
        mockReturnsService.setResponse(0, true);
        tick();
        fixture.detectChanges();
        expect(mockReturnsService.processReadySubscriptionAnnouced$Spy).toHaveBeenCalledTimes(1);
    }));

服务:

    private activateProcessReadySubscriptionSource = new Subject<number>();
    processReadySubscriptionAnnouced$ = this.activateProcessReadySubscriptionSource.asObservable();

    announceProcessReady(itemsInCart: number) {
        this.activateProcessReadySubscriptionSource.next(this.returnCartDataLength);
    }

我似乎无法弄清楚如何让订阅正确测试。

【问题讨论】:

    标签: angular unit-testing angular2-services


    【解决方案1】:

    (最后这是非常基本的东西,但我花了好几天才弄明白......希望能帮助那里的人节省一些时间:)......)

    我遇到了同样的问题,我可以解决它的唯一方法是使用 getter,以便能够在测试时返回模拟值...

    因此,在您的服务中,您必须将属性更改为 getter:

    private activateProcessReadySubscriptionSource = new Subject<number>();
    
    processReadySubscriptionAnnouced$ () { return this.activateProcessReadySubscriptionSource.asObservable();}
    

    之后,您必须修改访问该属性的方式才能(现在)在您的组件上执行它。

    现在您可以访问 .spec.ts 文件中的 observable subscribe 函数...

    我现在用代码告诉你我的类似历史:

    我有:

    /* * * * MyData.service.ts * * * */
    //   TYPING ASUMPTIONS...
    //   - DI = [Your defined interface]
    //   - BS = BehaviorSubject 
    @Injectable()
    export class MyDataService {
      private searchToggleSource: BS<DI> = new BS<DI>({ search: false });
      public currentToggleSearchStatus: Observable<DI> = this.searchToggleSource.asObservable();
    }
    
    
    /* * * * My.component.ts * * * */ 
    @Component({
      selector: 'my-component',
      template: './my.component.html',
      styleUrls: ['./my.component.css'],
    })
    export class MyComponent implements OnInit, OnDestroy {
      private searchToggleSubscription: Subscription;
      public search: boolean;
    
      // DataService being the injected, imported service 
      constructor(dataService: DataService){ }
    
      ngOnInit(){
        this.searchToggleSubscription = this.dataService.currentToggleSearchStatus
        .subscribe(
          ({ search }) => {
            this.search = search;
          });
      }
    
      ngOnDestroy(){
        this.searchToggleSubscription.unsubscribe();
      }
    }
    
    /* * * * My.component.spec.ts * * * */ 
    // ASUMPTIONS
    // - USING 'jest'
    describe('MyComponent', () => {
      let fixture: ComponentFixture<MyComponent>;
      let mockDataService;
    
      beforeEach(() => {
        mockDataService = createSpyObj('DataService', ['currentToggleSearchStatus', ]);
    
        TestBed.configureTestingModule({
          declarations: [
            MyComponent,
          ],
          providers: [
            { provide: DataService, useValue: mockDataService },
          ]
        });
        fixture = TestBed.createComponent(MyComponent);
      });
    
      it('should get state from DataService when ngOnInit', () => {
        mockDataService
        .currentToggleSearchStatus
        .mockReturnValue(of({search: true}));
    
        //... to call ngOnInit()
    
        // ****************** THE ERROR **************************
        // **** subscribe is not a function...
        // **** Since I have no access to a real Observable from
        // **** a fake DataService property...
    
        fixture.detectChanges();
    
        // *** SERIOUSLY I SPEND ALMOST 3 DAYS RESEARCHING AND PLAYING
        // *** WITH THE CODE AND WAS NOT ABLE TO FIND/CREATE A SOLUTION...
        // *** 'TILL LIGHT CAME IN...
        // *******************************************************
        expect(fixture.componentInstance.search).toBe(false)
      });
    });
    

    解决方案...使用吸气剂...我将使用 cmets '-' 来显示“修复”...

    /* * * * MyData.service.ts * * * */
    //   TYPING ASUMPTIONS...
    //   - DI = [Your defined interface]
    //   - BS = BehaviorSubject 
    @Injectable()
    export class MyDataService {
      private searchToggleSource: BS<DI> = new BS<DI>({ search: false });
      //------- CHANGED ---
      // public currentToggleSearchStatus: Observable<DI> = this.searchToggleSource.asObservable();
    
      //------- A GETTER ------ (MUST RETURN THE OBSERVABLE SUBJECT)
      public currentToggleSearchStatus(){
        return this.searchToggleSource.asObservable();
      }
    }
    
    
    /* * * * My.component.ts * * * */ 
    @Component({
      selector: 'my-component',
      template: './my.component.html',
      styleUrls: ['./my.component.css'],
    })
    export class MyComponent implements OnInit, OnDestroy {
      private searchToggleSubscription: Subscription;
      public search: boolean;
    
      // DataService being the injected, imported service 
      constructor(dataService: DataService){ }
    
      ngOnInit(){
        //------------ CHANGED  -------
        //this.searchToggleSubscription = this.dataService.currentToggleSearchStatus
        //.subscribe(
        //  ({ search }) => {
        //    this.search = search;
        //  });
    
        //------------ EXECUTE THE SERVICE GETTER -------
        this.searchToggleSubscription = this.dataService.currentToggleSearchStatus()
        .subscribe(
          ({search}) => {
            this.search = search;
          }
        );
      }
    
      ngOnDestroy(){
        this.searchToggleSubscription.unsubscribe();
      }
    }
    
    /* * * * My.component.spec.ts * * * */ 
    // ASUMPTIONS
    // - USING 'jest'
    describe('MyComponent', () => {
      let fixture: ComponentFixture<MyComponent>;
      let mockDataService;
    
      beforeEach(() => {
        mockDataService = createSpyObj('DataSharingSearchService', ['showHideSearchBar', 'currentToggleSearchStatus', ]);
    
        TestBed.configureTestingModule({
          declarations: [
            MyComponent,
          ],
          providers: [
            { provide: DataService, useValue: mockDataService },
          ]
        });
        fixture = TestBed.createComponent(MyComponent);
      });
    
      it('should get state from DataService when ngOnInit', () => {
        mockDataService
        .currentToggleSearchStatus
        .mockReturnValue(of({search: true}));
    
        //... to call ngOnInit()
        // ------------- NO ERROR :3!!!  -------------------
        fixture.detectChanges();
        expect(fixture.componentInstance.search).toBe(false)
      });
    });
    

    *** 注意:jest API 与 jasmine 非常相似...

    // jest:                    jasmine:
    //  createSpyObj       <=>  jasmine.createSpyObj
    // .mockReturnValue()  <=>  .and.returnValue()
    

    不要忘记只导入“of”函数,以在您的模拟服务中返回可观察对象...

    import { of } from "rxjs/observable/of";
    

    【讨论】:

    • 你也可以使用 spyOnProperty 代替。
    猜你喜欢
    • 1970-01-01
    • 2017-06-03
    • 2017-12-20
    • 2018-05-21
    • 1970-01-01
    • 2016-12-19
    • 1970-01-01
    • 1970-01-01
    • 2019-12-19
    相关资源
    最近更新 更多