【问题标题】:Unit testing with Angular 12 - Unable to function call inside ngFor loop使用 Angular 12 进行单元测试 - 无法在 ngFor 循环内调用函数
【发布时间】:2022-01-13 17:04:46
【问题描述】:

我是单元测试的新手,现在我无法调用 *ngFor 循环内的函数 angular 12 应用程序。有一个包含对象数组的可观察对象,它工作正常。整个数据被正确迭代。

这是我的 HTML 代码。

<div class="table-responsive">
    <table class="table table-striped">
        <thead>
          <tr>
            <th>#</th>
            <th>Name</th>
            <th>Category</th>
            <th>Action</th>
          </tr>
        </thead>
        <tbody>
          <tr *ngFor="let item of games$ | async; let i = index">
            <td>{{ i+1 }}</td>
            <td>{{ item.name }}</td>
            <td class="badge-cell">
               {{ item.categoryId }}
            </td>
            <td class="center action-cell">
              <button type="button"
                class="delete-game btn"
                (click)="deleteGame(item.id)">Delete</button>
            </td>
          </tr>
        </tbody>
    </table>
</div>
<!-- /.table-responsive-->

在循环内测试函数调用时会出现问题。因此,当我尝试访问fixture 的nativeElement 属性时,表的动态部分是null。因此daleteGame功能不可用。

这是console.log(fixture.nativeElement) 的样子:

<div _ngcontent-a-c74="" class="table-responsive">
    <table _ngcontent-a-c74="" id="dataTables-example" class="table table-striped">
        <thead _ngcontent-a-c74="">
            <tr _ngcontent-a-c74="">
                <th _ngcontent-a-c74="">#</th>
                <th _ngcontent-a-c74="">Name</th>
                <th _ngcontent-a-c74="">Category</th>
                <th _ngcontent-a-c74="">Action</th>
            </tr>
        </thead>
        <tbody>
         //----------NO ROWS IN THE LOG------------//
        <!--bindings={
          "ng-reflect-ng-for-of": null
        }-->
        </tbody>
    </table>
</div>

静态数据用于模拟原始数组(见GAME_DATA)。这是测试的样子:

describe('ListGamesComponent', () => {
  let component: ListGamesComponent;
  let fixture: ComponentFixture<ListGamesComponent>;

  beforeEach(
    waitForAsync(() => {
      TestBed.configureTestingModule({
        imports: [HttpClientTestingModule, CommonModule],
        declarations: [ListGamesComponent],
        providers: [
          RequestService,
        ],
      })
        .compileComponents()
        .then(() => {
          fixture = TestBed.createComponent(ListGamesComponent);
          component = fixture.componentInstance as any;
          component.games$ = from([GAME_DATA]) as unknown as Observable<
            IGameData[]
          >;
        });
    })
  );

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

  it('should check if deleteGame was called', fakeAsync(() => {
    spyOn(component, 'deleteGame');
    
    component.games$.subscribe((data: any) => {
     // ---- logged an observable, a proper list of games is returned ----
      console.log('data: ', data);
    });
    
    fixture.detectChanges();

    let button = fixture.debugElement.query(By.css('.delete-game'));
    
    button.triggerEventHandler('click', null);
    fixture.detectChanges();
    tick();

    expect(component.deleteGame).toHaveBeenCalled();
  }));
});

顺便说一句,我在测试中记录了一个 observable,它从 GAME_DATA 静态文件中检索整个数据,所以我不明白为什么没有生成整个表。

更新

games$ 在组件中是这样完成的:

export class ListGamesComponent implements OnInit {

  ngOnInit(): void {
    let pager: { index: number; size: number } = {
      index: this.pageIndex,
      size: this.pageSize,
    };
    this.games$ = this.gameService.getAllGames(pager).pipe(
      tap((data) => {
        this.totalRecords = data.totalItems;
        this.gamesList = data.games;
        
        this.setPage(1, false); // generate pagination
      }),
      pluck('games')
    );
  }
}

感谢任何帮助。谢谢!

【问题讨论】:

  • 组件中的games$是什么?可以显示组件代码吗?
  • 当然,我刚刚更新了帖子。

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


【解决方案1】:

在我们修复它之前,我会尝试向您解释问题所在,以便您更好地理解。

fixture.detectChanges() 的第一次调用是在调用ngOnInit 时(记住这一点非常重要)。

// we change the definition of games$
component.games$ = from([GAME_DATA]) as unknown as Observable<
            IGameData[]
          >;
// we subscribe and see the data
component.games$.subscribe((data: any) => {
     // ---- logged an observable, a proper list of games is returned ----
      console.log('data: ', data);
    });
// we call fixture.detectChanges(); and this calls ngOnInit
fixture.detectChanges();

问题在于ngOnInit 将this.games$ 重新分配给服务调用,因此丢失了先前分配的定义。这就是你看到这个问题的原因。我希望这是有道理的。

修复它:

我喜欢使用createSpyObj 来伪造外部服务的依赖关系,请遵循 !!在 cmets 中。

describe('ListGamesComponent', () => {
  let component: ListGamesComponent;
  let fixture: ComponentFixture<ListGamesComponent>;
  // !! create a mock game service
  let mockGameService: jasmine.SpyObj<GameService>;

  beforeEach(
    waitForAsync(() => {
      // !! assign mockGameService to a spy object with public method of
      // getAllGames
      mockGameService = jasmine.createSpyObj<GameService>('GameService', ['getAllGames']);
      TestBed.configureTestingModule({
        imports: [HttpClientTestingModule, CommonModule],
        declarations: [ListGamesComponent],
        providers: [
          RequestService,
         // !! provide fake GameService for the real GameService
         { provide: GameService, useValue: mockGameService },
        ],
      })
        .compileComponents()
        .then(() => {
          fixture = TestBed.createComponent(ListGamesComponent);
          component = fixture.componentInstance as any;          
        });
    })
  );

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

  it('should check if deleteGame was called', fakeAsync(() => {
    spyOn(component, 'deleteGame');
    
    // !! make the mock return a value before ngOnInit is called
    mockGameService.getAllGames.and.returnValue(from([GAME_DATA]) as unknown as Observable<
            IGameData[]
          >);
   
    fixture.detectChanges();

    let button = fixture.debugElement.query(By.css('.delete-game'));
    
    button.triggerEventHandler('click', null);
    fixture.detectChanges();
    tick();

    expect(component.deleteGame).toHaveBeenCalled();
  }));
});

查看this 链接,了解如何模拟外部依赖项。

【讨论】:

  • 非常感谢您的详细解释。我尝试了您的方法,但不幸的是得到了TypeError: Cannot read properties of null (reading 'triggerEventHandler') 错误,因为fixture.debugElement.query 再次为该css 选择器返回null。
  • 抱歉,我不知道为什么会这样。
猜你喜欢
  • 2021-10-07
  • 2019-07-31
  • 2020-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多