【问题标题】:Angular Unit Testing: Observable variable value not updating in the data service角度单元测试:可观察变量值未在数据服务中更新
【发布时间】:2020-09-21 00:40:24
【问题描述】:

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

例如:

图书服务:

...
private book: Book;
private bookSubject = new BehaviorSubject<Book>(this.book);
bookChanged = this.bookSubject.asObservable();
...

书籍组件:

...
book: Book;
ngOnInit() {
  this.bookService.bookChanged.subscribe(
    (book: Book) => this.book = book;
  )
}
...

组件的规格(测试文件):

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

所以测试失败是因为“book”变量没有用新值更新。

我在这里做错了什么?

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

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

【问题讨论】:

  • 另外,我已经尝试添加tick(),但仍然没有成功。
  • '应该换一本新书':这是对服务的测试还是对组件的测试?如果要测试组件(逻辑),可以设置book:component.book = newBook; fixture.detectChanges();
  • 好点@Marc!但我实际上想测试组件中的订阅是否按预期工作!原因:我想进一步测试一下,当服务中的值更新时,DOM 是否会自动更改
  • 听起来这是一个很好的端到端测试候选者:测试组件和服务。
  • @Marc 也许,但这将涉及我想避免的实际 API 调用。同样根据 Angular 的测试文档:angular.io/guide/testing#async-observables。这种东西是可以测试的,我就是想知道我做错了什么。

标签: angular unit-testing jasmine karma-jasmine angular-test


【解决方案1】:

我自己花了一段时间才弄清楚fixture.detectChanges(); 实际上是在启动组件的数据绑定。通过删除 fixture.detectChanges(); 下面应该仍然有效从 beforeEach() 并将其放入每个测试中,您正在等待启动与组件的任何数据绑定。

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

【讨论】:

    【解决方案2】:

    添加component.ngOnInit(); 使我的测试成功运行。

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

    这是一个好习惯吗?还是我应该创建一个间谍等?

    【讨论】:

      【解决方案3】:

      这是发生的顺序:

        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) 移动到更早的位置。

      【讨论】:

      • 谢谢,但我有两个测试,一个是原书(通过),一个是更新的书(失败,因为这本书永远不会更新)!还有,组件里有订阅,应该更新书吧?
      • 此外,如果我添加第三个随机测试,那么我会在其中获得书籍的新价值。很奇怪!
      • @PranavBhatia 另外,组件中有订阅,所以它应该更新书吧? - 不正确。使用这一行bookServiceStub.bookChanged = of(newBook),您将覆盖模拟服务中的引用,而组件订阅了旧对象。是的,在测试中显式运行 ngOnInit 也会重新运行这一行 this.bookService.bookChanged.subscribe(//...,这将(在某种程度上)解决问题(现在您有两个订阅 - 旧订阅和新订阅)。
      • 谢谢,我现在明白了!但是,这是一种正确的测试方式还是我应该遵循其他一些代码风格?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-08-05
      • 1970-01-01
      • 2021-05-20
      • 2018-06-03
      • 1970-01-01
      • 2021-04-11
      • 2020-03-11
      相关资源
      最近更新 更多