Angular 角度单元测试:数据服务中未更新的可观察变量值

Angular 角度单元测试:数据服务中未更新的可观察变量值,angular,unit-testing,jasmine,karma-jasmine,angular-test,Angular,Unit Testing,Jasmine,Karma Jasmine,Angular Test,我有一个组件使用的数据服务 beforeEach(() => { fixture = TestBed.createComponent(BookComponent); component = fixture.componentInstance; }); it('should create', () => { fixture.detectChanges(); expect(component).toBeTruthy(); }); it

我有一个组件使用的数据服务

  beforeEach(() => {
    fixture = TestBed.createComponent(BookComponent);
    component = fixture.componentInstance;
  });

  it('should create', () => {
    fixture.detectChanges();
    expect(component).toBeTruthy();
  });
  it('should change the book with a new one', fakeAsync(() => {
    const newBook = {
      id: 769,
      name: 'Book 2'
    };
    bookServiceStub.bookChanged = of(newBook);
    fixture.detectChanges();
    expect(component.book.id).toBe(newBook.id);
    expect(component.book).toBe(newBook);
  }));
例如:

图书服务:

...
private book: Book;
private bookSubject = new BehaviorSubject<Book>(this.book);
bookChanged = this.bookSubject.asObservable();
...
组件的规格(测试文件):

describe('BookComponent', () => {
  let component: BookComponent;
  let fixture: ComponentFixture<BookComponent>;
  let bookServiceStub: Partial<BookService>;

  bookServiceStub = {
    bookChanged: of({id: 123, name: 'Book 1'})
  };
  beforeEach(async(() => {
    TestBed
      .configureTestingModule({
        declarations: [BookComponent],
        providers: [
          {provide: BookService, useValue: bookServiceStub},
          ...
        ]
      })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(BookComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should change the book with a new one', fakeAsync(() => {
    const newBook = {
      id: 769,
      name: 'Book 2'
    };
    bookServiceStub.bookChanged = of(newBook);
    fixture.detectChanges();
    fixture.whenStable().then(() => {
      fixture.detectChanges();
      expect(component.book.id).toBe(newBook.id); // FAILS (still has old value)
      expect(component.book).toBe(newBook); // FAILS (still has old value)
    });
  }));
});

description('BookComponent',()=>{
let组件:BookComponent;
let夹具:组件夹具;
让bookservicesbub:部分;
bookServiceStub={
bookChanged:of({id:123,名称:'Book 1'})
};
beforeach(异步(()=>{
试验台
.配置测试模块({
声明:[BookComponent],
供应商:[
{provide:BookService,useValue:bookServiceStub},
...
]
})
.compileComponents();
}));
在每个之前(()=>{
fixture=TestBed.createComponent(BookComponent);
组件=fixture.componentInstance;
fixture.detectChanges();
});
它('应该创建',()=>{
expect(component.toBeTruthy();
});

它('should change the book with a new's',fakeAsync(()=>{ const newBook={ id:769, 书名:“第二册” }; bookServiceStub.bookChanged=of(newBook); fixture.detectChanges(); fixture.whenStable()然后(()=>{ fixture.detectChanges(); expect(component.book.id).toBe(newBook.id);//失败(仍有旧值) expect(component.book).toBe(newBook);//失败(仍有旧值) }); })); });
因此测试失败,因为“book”变量没有用新值更新

我做错了什么?

注意:我实际上想测试组件中的订阅是否按预期工作


原因:我想签入进一步的测试,当服务中的值更新时,DOM是否会自动更改

  beforeEach(() => {
    fixture = TestBed.createComponent(BookComponent);

// The component is created now. So, its constructor is run and services instantiated.
// Also, ngOnInit is executed.

// ...

  it('should change the book with a new one', fakeAsync(() => {
    const newBook = {
      id: 769,
      name: 'Book 2'
    };

// And now you're overwriting bookServiceStub's bookChanged property.
// The problem is, the component doesn't care at this point, it already has a subscription,
// and it's attached to the original bookChanged stream.

    bookServiceStub.bookChanged = of(newBook);


如果您在此套件中没有更多的测试要运行(或者如果您不需要其中不同的
bookChanged
),您只需在创建组件之前将
bookServiceStub.bookChanged=of(newBook)
提前移动即可。

添加
component.ngonit()使我的测试成功运行

it('should change the book with a new one', fakeAsync(() => {
    const newBook = {
      id: 769,
      name: 'Book 2'
    };
    bookServiceStub.bookChanged = of(newBook);
    component.ngOnInit(); // ADDED HERE
    fixture.detectChanges();
    expect(component.book.id).toBe(newBook.id);
    expect(component.book).toBe(newBook);
  }));

不过,这是一种好的做法吗。还是我应该创建一个间谍,等等?

我自己花了一段时间才弄明白,实际上是启动了组件的数据绑定。通过移除夹具,以下各项应能正常工作。detectChanges();从beforeach()开始,并将其放入每个测试中,您将等待启动到组件的任何数据绑定

  beforeEach(() => {
    fixture = TestBed.createComponent(BookComponent);
    component = fixture.componentInstance;
  });

  it('should create', () => {
    fixture.detectChanges();
    expect(component).toBeTruthy();
  });
  it('should change the book with a new one', fakeAsync(() => {
    const newBook = {
      id: 769,
      name: 'Book 2'
    };
    bookServiceStub.bookChanged = of(newBook);
    fixture.detectChanges();
    expect(component.book.id).toBe(newBook.id);
    expect(component.book).toBe(newBook);
  }));

此外,我还尝试添加了tick(),但仍然没有成功。“应该用一本新的书来改变这本书”:这是对服务的测试还是对组件的测试?如果要测试组件(逻辑),可以设置book:component.book=newBook;fixture.detectChanges();马克说得好!但我实际上想测试组件中的订阅是否按预期工作!原因:我想签入进一步的测试,当服务中的值被更新时,DOM是否会自动更改?听起来这是E2E测试的一个很好的候选者:测试组件和服务。@Marc可能,但这会涉及实际的API调用,我想避免。同样根据Angular的测试文件:。这类东西是可以测试的,我只是想知道我做错了什么。谢谢,但我有两个测试,一个是原版书(通过),一个是更新的书(失败,因为这本书从未更新过)!另外,组件中有一个订阅,因此它应该更新图书,对吗?此外,如果我添加第三个随机测试,那么我将在其中获得图书的新值。真奇怪@PranavBhatia另外,组件中有订阅,因此它应该更新该书,对吗不准确的使用这一行
bookServiceStub.bookChanged=of(newBook)
您将覆盖模拟服务中的引用,而组件将订阅旧对象。是的,在测试中显式运行
ngOnInit
也将重新运行这一行
this.bookService.bookChanged.subscribe(//…
),这将(在一定程度上)解决问题(现在您有两个订阅-旧的和新的)。谢谢,我现在明白了!但是,这是一种正确的测试方法还是应该遵循其他代码样式?