Skip to content

fixture.whenStable does not resolve with pending resource/rxResource #60742

Closed as not planned

Description

@hansmaad

Which @angular/* package(s) are the source of the bug?

core

Is this a regression?

No

Description

I don't know if this is by design, but the behavior is very different from when working with plain observables or promises.
Consider a simple component

export class TestComponent  {
  stuff: ResourceRef<string | undefined>;
  constructor(service: StuffService) {
    this.stuff = resource({
      loader: () => firstValueFrom(service.stuff$)
    });
  }
}

If we write a simple test case that wants to check the state before the first resource value is loaded, the call to whenStable() won't resolve and the test case times out.

  const stuff$ = new ReplaySubject<string>(1);
  await TestBed.configureTestingModule({
      imports: [TestComponent],
      providers: [{
        provide: StuffService, useValue: { stuff$ }
      }]
  }).compileComponents();

  const fixture = TestBed.createComponent(TestComponent);
  fixture.detectChanges();
  await fixture.whenStable();
  expect(componentIsInStateBeforeStuffIsLoaded()).toBeTrue();
  stuff$.next('hello');
  fixture.detectChanges();
  await fixture.whenStable();
  expect(componentIsInStateAfterStuffIsLoaded()).toBeTrue();

The same happens with rxResource.

I would consider a component to be in a stable state while resource is loading the next value. At least it was always the case with asynchronous processes that it's fine to call fixture.whenStable() as many times you want.

This example shows the different behavior when using resource or and async pipe on the source observable directly: https://stackblitz.com/edit/stackblitz-starters-yklwg5m5?file=src%2Fmain.ts

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/stackblitz-starters-yklwg5m5?file=src%2Fmain.ts

Please provide the exception or error you saw


Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 19.2.4
Node: 22.13.0
Package Manager: npm 10.9.2
OS: win32 x64

Angular: 19.2.3
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1902.4
@angular-devkit/build-angular   19.2.4
@angular-devkit/core            19.2.4
@angular-devkit/schematics      19.2.4
@angular/cli                    19.2.4
@schematics/angular             19.2.4
rxjs                            7.8.2
typescript                      5.7.3
zone.js                         0.15.0

Anything else?

No response

Activity

  1. JeanMeche commented on Apr 4, 2025

    @JeanMeche
    Member

    This is by design, your resource is awaiting its value and by nature keeps the app unstable.

  2. hansmaad commented on Apr 4, 2025

    @hansmaad
    Author

    Ok, I would consider @if (res.isLoading()) { ... } a stable component state that last until it switches to false, but I don't have to question that design decision here. I noticed that I can use fakeAsync and tick to make my expectations during that "unstable" state. Is this the recommended solution?

  3. atscott commented on Apr 4, 2025

    @atscott
    Contributor

    Ok, I would consider @if (res.isLoading()) { ... } a stable component state that last until it switches to false

    This would mean apps would serialize with loaders rather than having all their resource data in the loaded state.

    I noticed that I can use fakeAsync and tick to make my expectations during that "unstable" state. Is this the recommended solution?

    You can use a mock clock or wait real time. Something like waitFor is particularly useful and allows you to write unit tests in the same style as you would an e2e test (you could even use the same test body between environments).

  4. hansmaad commented on Apr 4, 2025

    @hansmaad
    Author

    We used to have test suites like this a lot when testing components that do async stuff

     describe('when click on load button', () => {
       beforeEach(clickLoadButtonAndStabelize);
    
        it('should show loading spinner', () => expect(getSpinner()).toBeTruthy());
        it('should keep showing current data', () => expect(getData()).toBe(expectedData);
    
        describe('when data was loaded', () => {
           beforeEach(flushRemoteDataAndStabelize);
           it('should hiding loading spinner', () => expect(getSpinner()).toBeFalsy());
           it('should show new data', () => expect(getData()).toBe(newData);
        });
    });
    

    It worked well with await fixture.whenStable() regardless of which async/reactive style you took, Promises, Observables, Signals, async pipe. But if you wrap this in a resource or rxResource the behaviour of whenStable is different. I, as a developer, found this very suprising and I thought it's probably just buggy in the current state.
    I'm on Angular(js) since v1.2, and to be honest, I've never seen a whenStable() promise that did not resolve, or something that holds a component unstable forever... So that one was just surprising ;)

  5. added this to the needsTriage milestone on Apr 4, 2025
  6. marwesfur commented on May 28, 2025

    @marwesfur

    The testing scenario @hansmaad describes is very typical for our applications as well.

    To make things worse, CDK ComponentHarnesses automatically call whenStable when auto change detection is not disabled (see https://github.com/angular/components/blob/main/src/cdk/testing/testbed/testbed-harness-environment.ts#L84 and https://github.com/angular/components/blob/main/src/cdk/testing/testbed/testbed-harness-environment.ts#L174) which then always causes tests to timeout when resources in loading state are involved.

    @atscott I can't fully agree to that one:

    This would mean apps would serialize with loaders rather than having all their resource data in the loaded state.

    To me it seems typical that an application loads from several independent data sources in parallel (using the resource api) to improve perceived performance -- yet some actions might depend on the loading state of more than one such data (re)source. Testing this in a straightfoward manner is currently impossible thanks to that (imo) questionable design decision.

    I might have misunderstood your argument, though...

    @hansmaad

    but I don't have to question that design decision here.

    respect for your humbleness 😄

  7. atscott commented on May 28, 2025

    @atscott
    Contributor

    To me it seems typical that an application loads from several independent data sources in parallel (using the resource api) to improve perceived performance -- yet some actions might depend on the loading state of more than one such data (re)source.

    This seems separate from the SSR use-case. Loading data in parallel has nothing to do the server waiting for stabilization before serializing.

    questionable design decision.

    IMO, the decision that is in question is the one to wait for stability in the TestBed harnesses. The opt out you have is wrapping some code in manualChangeDetection where it would have been waiting for stability.

  8. marwesfur commented on May 28, 2025

    @marwesfur

    This seems separate from the SSR use-case. Loading data in parallel has nothing to do the server waiting for stabilization before serializing.

    Ok for server side rendering, yes, I can follow why it makes sense to treat an application with loading resources as unstable. I was fully focused on client side rendering (as was the OP I assume), that's why I misunderstood your point.

    And I hope that you can at least also understand why this design causes inconvenience for testing in client side rendering scenarios, in particular because

    await fixture.whenStable() [always worked well] regardless of which async/reactive style you took, Promises, Observables, Signals, async pipe.

    Bottom line: I'll keep using the manualChangeDetection workaround. Thanks for you explanations.

  9. jermowery commented on Aug 17, 2025

    @jermowery
    Contributor

    This also caused problems for me both inside and outside of g3. It is just unclear that the pending resource state would cause this behavior and it is unclear that the harnesses are waiting for stability.

    I am glad that we have a workaround, but it would be good to signal (pun intended) this more clearly in the resource docs. The test scenarios described here are extremely common. I have personally done and reviewed code where people abandoned using resource because tests like the ones described here started failing and it was very unclear why and was hand waved as "resource is experimental and not ready to use"

  10. wawyed commented on Nov 3, 2025

    @wawyed

    Is there going to be any updates to handle this better? I'm concerned about using rxResource if we can't integrate properly with harnesses

  11. atscott commented on Nov 3, 2025

    @atscott
    Contributor

    For harnesses, you can use manualChangeDetection to prevent them from waiting for stability. There are no planned changes in the framework to make tests work differently from production with respect to stability.

  12. wawyed commented on Nov 3, 2025

    @wawyed

    For harnesses, you can use manualChangeDetection to prevent them from waiting for stability. There are no planned changes in the framework to make tests work differently from production with respect to stability.

    Would it be possible to add a way to disable it for the whole loader? that would make it a little bit easier than having to manually add it to every it/bEach that uses a harness

  13. JeanMeche commented on Jun 8, 2026

    @JeanMeche
    Member

    I'll closing this as a dupe of #68808 where comments mention how to better test resources.

  14. angular-automatic-lock-bot commented on Jul 9, 2026

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  15. locked and limited conversation to collaborators on Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: docsRelated to the documentationcross-cutting: resourceIssues related to the newly introduced resource / httpResource

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions