angular

Angular computed signal in component does not update when service signal changes


Why does expect(component['numberHogeNames']()).toBe(3) fail in my Angular test?

Here is a simplified version of my code:

// hoge-service.ts
interface HogeData {
  id: string;
  name: string;
}

export class HogeService {
  private dataSrc = signal<HogeData[]>([
    { id: '1', name: 'kage' },
    { id: '2', name: 'sage' },
    { id: '3', name: 'tage' },
    { id: '4', name: 'nage' },
    { id: '5', name: 'mage' },
    { id: '6', name: 'yage' },
    ...
  ]);

  numberHogeNames = computed<number>(() => {
    return this.dataSrc().filter((item) => item.name === 'hoge').length;
  });
}
// hoge-component.ts
@Component({
  standalone: true,
  providers: [HogeService],
  template: `<div class="num">{{ numberHogeNames() }}</div>`,
  encapsulation: ViewEncapsulation.Emulated,
  changeDetection: ChangeDetectionStrategy.OnPush,
  ...
})
export class HogeComponent {
  private hogeService = inject(HogeService);

  protected numberHogeNames = computed<number>(() => {
    return this.hogeService.numberHogeNames();
  });
}
// hoge-component.spec.ts
describe('Hoge Test', () => {
  let component: HogeComponent;
  let fixture: ComponentFixture<HogeComponent>;
  let hogeService: HogeService;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [HogeComponent],
      providers: [HogeService],
    }).compileComponents();

    fixture = TestBed.createComponent(HogeComponent);
    component = fixture.componentInstance;
    hogeService = TestBed.inject(HogeService);
  });

  it('Case 01', async () => {
    hogeService['dataSrc'].update((current) => {
      current.forEach((item, index) => {
        if (index < 3) item.name = 'hoge';
      });
      return [...current];
    });

    // Success
    expect(hogeService.numberHogeNames()).toBe(3);

    fixture.detectChanges();

    // Failed (Expected 0 to be 3.)
    expect(component['numberHogeNames']()).toBe(3);
  })
});

Despite the test failure, the component works correctly when used in the actual application. The value of numberHogeNames on the screen updates as expected when dataSrc changes.

I tried modifying the private dataSrc signal in the HogeService by using update() to change some items' name properties to hoge. After updating dataSrc, I expected both hogeService.numberHogeNames() and component['numberHogeNames']() to return 3 since there are three items with the name hoge.

In the test, hogeService.numberHogeNames() updates correctly to 3, which is the expected behavior. However, component['numberHogeNames']() still returns the old value and does not update as expected.

I have tried using fixture.detectChanges() and waiting for a short delay using setTimeout to see if the component’s computed signal would update, but these attempts did not resolve the issue.

I expect the component’s computed signal (numberHogeNames) to reflect the changes in the service's signal (dataSrc) just as it does in the actual application where everything works as intended.


Solution

  • You need to reference the service that is injected into the component (i.e. component.hogeService):

        component.hogeService['dataSrc'].update((current) => {
          current.forEach((item, index) => {
            if (index < 3) item.name = 'hoge';
          });
          return [...current];
        });
    

    Then later:

    expect(component.hogeService.numberHogeNames()).toBe(3);
    

    The test after these changes should pass.