带.asObservable()的Angular 2单元测试(TestBed)服务

时间:2017-06-23 19:35:39

标签: angular unit-testing angular2-services

我有一个调用服务的组件,以查看是否已从其他组件宣布订阅。

组件:

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

当我尝试测试时,我收到一个错误:

  

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

SPEC

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);
    }

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

1 个答案:

答案 0 :(得分:0)

(最后这是非常基本的内容,但是花了我几天的时间才弄清楚……希望能帮助那里的人节省一些时间:)……)

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

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

private activateProcessReadySubscriptionSource = new Subject<number>();

processReadySubscriptionAnnouced$ () { return this.activateProcessReadySubscriptionSource.asObservable();}

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

现在您可以访问.spec.ts文件上的可观察订阅功能...

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

我有:

/* * * * 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)
  });
});

解决方案...使用吸气剂...我将使用注释“-”显示“解决方法” ...

/* * * * 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与茉莉花非常相似...

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

别忘了仅导入'of'函数,以在模拟服务中返回可观察对象...

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