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

K T*_*mas 6 unit-testing angular2-services angular

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

成分:

this.activateProcessReadySubscription =  this.returnService.processReadySubscriptionAnnouced$.subscribe(
            itemsInCart => {
                this.itemsInCart = itemsInCart;
            });
Run Code Online (Sandbox Code Playgroud)

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

类型错误:无法读取未定义的属性“订阅”

规格

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

服务:

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

    announceProcessReady(itemsInCart: number) {
        this.activateProcessReadySubscriptionSource.next(this.returnCartDataLength);
    }
Run Code Online (Sandbox Code Playgroud)

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

Jes*_*eon 2

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

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

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

private activateProcessReadySubscriptionSource = new Subject<number>();

processReadySubscriptionAnnouced$ () { return this.activateProcessReadySubscriptionSource.asObservable();}
Run Code Online (Sandbox Code Playgroud)

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

现在您可以访问 .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)
  });
});
Run Code Online (Sandbox Code Playgroud)

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

/* * * * 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)
  });
});
Run Code Online (Sandbox Code Playgroud)

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

// jest:                    jasmine:
//  createSpyObj       <=>  jasmine.createSpyObj
// .mockReturnValue()  <=>  .and.returnValue()
Run Code Online (Sandbox Code Playgroud)

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

import { of } from "rxjs/observable/of";
Run Code Online (Sandbox Code Playgroud)