You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何对AG Grid gridApi.forEachNode内部逻辑进行单元测试?

问题描述

在Angular 15、Jest 29.5.0、ag-grid-angular 25.3.0环境下,需要为以下组件方法实现单元测试覆盖:

processUpdates(input) {
  let request = [];
  this.gridApi.forEachNode(function (node) { // 需要mock/spy此方法的内部逻辑
    if (input.has(data.id)) {
      request.push(this.createRequestRowUpdate(node, input));
    }
    this.someCoolService.saveMychanges(request).pipe(map(res => res as any)).subscribe(res => {
      // 成功逻辑
    }, (error: HttpErrorResponse) => {
      // 失败逻辑
    });
  });
}

目前已mock gridApi,但forEachNode的mock未完善,需要编写测试用例对forEachNode内部调用的createRequestRowUpdate、saveMychanges方法进行断言(例如验证saveMychanges是否被调用一次)。当前测试代码如下:

const mGridApi = {
  // 其他已mock方法
  forEachNode: function () { // 需完善此mock
  },
  // 其他已mock方法
};

it('should call processUpdate', async () => {
   component['gridApi'] = mGridApi;
   jest.spyOn(component, 'processUpdate');
   jest.spyOn(component, 'createRequestRowUpdate');
   jest.spyOn(service, 'saveMychanges');

   component['processUpdate']('test data in here');
   expect(component['createRequestRowUpdate']).hasBeenCalledOnce()
});

请教如何设置测试以实现上述断言?


解决方案

1. 完善forEachNode的Mock,执行传入的回调

forEachNode的核心逻辑是调用传入的回调函数,所以mock时要让它主动执行这个回调,同时要处理回调里的this指向问题——原代码用了普通函数,this默认指向gridApi,得手动绑定组件实例才能正确调用createRequestRowUpdate和someCoolService。

修改后的mGridApi:

const mGridApi = {
  // 其他已mock方法
  forEachNode: jest.fn((callback) => {
    // 模拟ag-grid的node数据,根据测试场景自定义结构
    const mockNode = { data: { id: 'test-id' } };
    // 把组件实例绑定到回调的this上
    callback.call(component, mockNode);
  }),
  // 其他已mock方法
};

2. 构造符合条件的测试输入

原代码里有input.has(data.id)的判断,测试时要构造能触发(或不触发)这个判断的输入,比如用Set模拟:

// 包含mockNode的id,会触发createRequestRowUpdate调用
const validInput = new Set(['test-id']);
// 不包含mockNode的id,不会触发createRequestRowUpdate调用
const invalidInput = new Set(['non-exist-id']);

3. 处理异步Observable的订阅

saveMychanges返回的是Observable,测试时要让它立即完成,否则断言会在订阅执行前触发。可以让spy返回一个立即发出值的Observable:

// 导入rxjs的of操作符
import { of, throwError } from 'rxjs';

// 让saveMychanges返回一个成功的Observable
jest.spyOn(service, 'saveMychanges').mockReturnValue(of({}));
// 如果要测试错误场景,返回错误Observable
// jest.spyOn(service, 'saveMychanges').mockReturnValue(throwError(() => new HttpErrorResponse({})));

4. 完整测试用例示例

整合以上步骤,完整测试代码如下:

import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { of, throwError } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
import { YourComponent } from './your.component';
import { SomeCoolService } from './some-cool.service';

describe('YourComponent - processUpdates', () => {
  let component: YourComponent;
  let service: SomeCoolService;
  const mGridApi = {
    forEachNode: jest.fn((callback) => {
      const mockNode = { data: { id: 'test-id' } };
      callback.call(component, mockNode);
    }),
    // 其他已mock的gridApi方法
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [YourComponent],
      providers: [SomeCoolService]
    });
    component = TestBed.inject(YourComponent);
    service = TestBed.inject(SomeCoolService);
    // 绑定mock的gridApi到组件
    component['gridApi'] = mGridApi;
  });

  it('should call createRequestRowUpdate and saveMychanges when input contains node id', () => {
    // 创建方法spy
    const createSpy = jest.spyOn(component, 'createRequestRowUpdate').mockReturnValue({ mock: 'data' });
    const saveSpy = jest.spyOn(service, 'saveMychanges').mockReturnValue(of({}));

    // 调用目标方法
    component['processUpdates'](new Set(['test-id']));

    // 断言逻辑
    expect(mGridApi.forEachNode).toHaveBeenCalled();
    expect(createSpy).toHaveBeenCalledTimes(1);
    expect(createSpy).toHaveBeenCalledWith(mGridApi.forEachNode.mock.calls[0][0].arguments[0], new Set(['test-id']));
    expect(saveSpy).toHaveBeenCalledTimes(1);
    expect(saveSpy).toHaveBeenCalledWith([{ mock: 'data' }]);
  });

  it('should NOT call createRequestRowUpdate when input does not contain node id', () => {
    const createSpy = jest.spyOn(component, 'createRequestRowUpdate');
    const saveSpy = jest.spyOn(service, 'saveMychanges').mockReturnValue(of({}));

    component['processUpdates'](new Set(['non-exist-id']));

    expect(createSpy).not.toHaveBeenCalled();
    expect(saveSpy).toHaveBeenCalledTimes(1);
    expect(saveSpy).toHaveBeenCalledWith([]); // 此时request是空数组
  });

  it('should handle save failure correctly', fakeAsync(() => {
    // 模拟错误场景
    const errorSpy = jest.spyOn(console, 'error').mockImplementation();
    jest.spyOn(service, 'saveMychanges').mockReturnValue(throwError(() => new HttpErrorResponse({ status: 400 })));

    component['processUpdates'](new Set(['test-id']));
    tick(); // 触发Observable的错误回调

    expect(errorSpy).toHaveBeenCalled();
    errorSpy.mockRestore();
  }));
});

5. 额外注意事项

  • 原代码里的data.id应该是node.data.id(看起来像是笔误),要确保mockNode的结构和业务代码匹配。
  • 如果测试涉及异步逻辑,建议用Angular的fakeAsync和tick()来确保订阅完成后再执行断言。

内容的提问来源于stack exchange,提问作者null-point-exceptional

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 21:51:17