Skip to content

How to test an httpResource triggering an httpResource? #68808

Open

Description

@rkrisztian

Describe the problem that you experienced

I am trying to write a test case for when the result of an httpResource would trigger another httpResource, without having a component context (that is, when testing a service). For now I am failing to find a way to make the following test work:

Enter the URL of the topic with the problem

https://angular.dev/guide/http/http-resource#testing-an-httpresource

Describe what you were looking for in the documentation

I was looking for details on how I can control the triggering of the loading of individual httpResources and waiting for them to load one by one.

Describe the actions that led you to experience the problem

Let's say I have an app that administers a catalog of dogs where the app on load would not only load the list of dogs but also automatically select the first dog and open its details. And once I added this automatic selection of the first dog with linkedSignal, my tests started breaking, even if the tests had nothing to do with checking the dog details.

Describe what you want to experience that would fix the problem

The documentation should tell more info about how we can resolve situations like this when there is a chain of httpResource requests where one triggers the other, so that the app can eventually become stable. As a follow-up problem, I should be able to be in control of whether my test wants the next request to be triggered.

Provide any additional information here in as much as detail as you can

My test code used Angular 20, but the problem is also reproducible with Angular 21.

Activity

  1. added this to the needsTriage milestone on May 20, 2026
  2. added this to the needsTriage milestone on May 20, 2026
  3. JeanMeche commented on May 20, 2026

    @JeanMeche
    Member

    You can't really on stability here because of the interlocking it causes (the first resource resolving, triggers the other resource).

    The best alternative is to wait/poll for the resource value.

    For example with vitest:

        await vi.waitUntil(() => selectedDog() !== undefined);
    
  4. rkrisztian commented on May 20, 2026

    @rkrisztian
    Author

    Accidentally closed. Sorry, I'm still elaborating my response.

  5. rkrisztian commented on May 20, 2026

    @rkrisztian
    Author

    Thank you, @JeanMeche, your solution works very well. It can also be used to wait for httpResources:

          await vi.waitUntil(() => !dogs.isLoading());
          await vi.waitUntil(() => dogs.hasValue());

    Both will trigger the HTTP request for the selectedDogDetails, which then can now be mocked.

    I created an Angular 21 + Vitest-based example to update my example code:

    https://stackblitz.com/edit/angular-vitest-starter-v21-gx9buvbk?file=src%2Fexample.spec.ts

    This kind of selective waiting, I think, is what is missing from the documentation. Relying on await applicationRef.whenStable(); is not the most worthwhile method for complex applications, in my opinion. You might end up needing to mock more than you need. I think vi.waitUntil is a great way to do that, and it wasn't obvious to me that it works so well. Including this in the documentation as alternative would be great, IMO.

  6. JeanMeche commented on May 20, 2026

    @JeanMeche
    Member

    Thank you for your feedback. We indeed plan to rewamp our testing docs and include more pragmatic testing examples.

  7. yjaaidi commented on May 20, 2026

    @yjaaidi
    Contributor

    Hi @rkrisztian, indeed, in most cases, polling is the way to go.

    expect.poll + waiting for http request

    By the way, you can simplify the tests a little more using expect.poll and also by waiting for the HTTP request to happen instead. See https://stackblitz.com/edit/angular-vitest-test-example-for-chaining-http-resource?file=src%2Fexample.spec.ts

    const reqs = await waitUntilRequestsMatch({ method: 'GET' });
        reqs[0].flush([
          { id: 1, name: 'Teddy' },
          { id: 2, name: 'Cooper' },
        ]);
    reqs[0].flush(...);
    
    async function waitUntilRequestsMatch(
      match: string | RequestMatch | ((req: HttpRequest<any>) => boolean)
    ) {
      return vi.waitUntil(() => {
        const reqs = TestBed.inject(HttpTestingController).match(match);
        return reqs.length > 0 ? reqs : undefined;
      });
    }

    and

    await expect
      .poll(() => selectedDogDetails.value())
      .toHaveProperty('age', 4);  

    HttpTestingController vs. a declarative network fake such as MSW

    Except for some very rare use cases, I would highly recommend to avoid using HttpTestingController.
    Here is why:

    • IMO, its "imperative" API makes tests highly coupled to implementation details and encourages us to write over-specifying tests. In other words, in most cases, we want to test the behavior (i.e. fetching the right dog), not which http calls were made and in which order.
    • Even when we do not care about the order of the calls, it tends to force us to program each response in the right order.
    • If we cared about the order of the calls then, it would be much easier to assert the state of the signals and just check the requests "log" at the end if needed.
    • Its design encourages us to program a specific response for each request, instead of a behavior that mimics the server and that would be reusable across tests.

    That's why, if you really need to "mock" at the HTTP level, I would highly recommend implementing a reusable fake.
    A common alternative is MSW. This makes tests much more readable / maintainable / behavior-focused / cheaper to maintain.

    See https://stackblitz.com/edit/mocking-network-with-angular-vitest-msw?file=src%2Fdog-store.spec.ts

    The tests looks like this (and the whole thing is more type-safe):

    test(`🐶 that's my puppy`, async () => {
      const server = await setUpFakeDogHttpServer();
    
      server.setDogs([
        {
          id: 'dog-teddy',
          name: 'Teddy',
          diet: 'dog-food'
        },
        {
          id: 'dog-cooper',
          name: 'Cooper',
          diet: 'wooden-sticks'
        },
      ]);
    
      const dogStore = TestBed.inject(DogStore);
    
      await expect.poll(() => dogStore.selectedDogDetails.value()?.diet).toBe('dog-food');
    });

    and the reusable MSW fake looks like this:

    async function setUpFakeDogHttpServer() {
      let _dogs: DogDetails[] = [];
    
      const worker = setupWorker(
        http.get('/api/dogs', () => HttpResponse.json<Dog[]>(_dogs.map(d => ({
          id: d.id,
          name: d.name
        })))),
        http.get('/api/dogs/:dogId', async ({params}) => {
          const dog = _dogs.find(d => d.id === params['dogId']);
    
          return dog ? HttpResponse.json<DogDetails>(dog) : new HttpResponse(null, {status: 404});
        })
      );
    
      await worker.start({quiet: true});
      onTestFinished(() => worker.stop());
    
      return {
        setDogs(dogs: DogDetails[]) {
          _dogs = dogs;
        }
      }
    }

    In the Stackblitz, you can even see how to pause the details request so that you can test the state of the resources/signals or components when the resource is loading.

    "Mocking" http vs. abstraction layer (this is more conceptual)

    More generally speaking, I would recommend to avoid "mocking" at the HTTP level for the following reasons:

    • The HTTP API is an infrastructure implementation detail. The interface can change at any moment. The behavior too. Something has to make sure that the mocks behave like the real API.
    • It couples tests to the infrastructure. (Ex. when the types changes, this will affect many tests).
    • As consumers of an HTTP API, we do not necessarily have the expertise of its behavior. (Ex. what happens when I add 10 dogs, does it return a 403 because I have to pay first before adding more dogs there?)

    It is better to provide fakes at an abstraction layer with core types that you totally control.
    For example, a DogRepositoryFake service that creates Angular resources with in memory data with your types.
    The real implementation can use httpResource.
    The problem then is how do you make sure the real implementation works.
    Well it can be in the happy path of wider tests such as end-to-end, or through contract tests. A test that runs on both the fake implementation and against the real backend to make sure that they behave the same.
    Here is an example of this: https://github.com/yjaaidi/kitchen/tree/ashes-to-ashes-specs-to-specs/apps/whiskmate/src/app/recipe/recipe-repository

    Sorry for the shameless plug, but if you want to dive deeper into this, you can read this: https://cookbook.marmicode.io/angular/testing/fake-it-till-you-mock-it

    I hope this helps.

  8. rkrisztian commented on May 21, 2026

    @rkrisztian
    Author

    @yjaaidi, thank you, I'm going to need time to process all the information, but very cool!

  9. rkrisztian commented on Jun 10, 2026

    @rkrisztian
    Author

    In the meantime, I adopted MSW in my project. I know it's not exactly what you suggested as most ideal, but it's a step forward, as I got a much cleaner testing setup:

    • I could increase my coverage because I didn't have to mock service classes.
    • I used to have chicken-and-egg problems with the HTTP testing controller, because the render function I used for Vitest browser mode sometimes happened to render a component that made a HTTP request on first render, before I could inject the HTTP testing controller to expect requests before they are made. Because of this, I needed to mock the httpResource object, which was ugly.

    The details of my challenges are in this commit in case it's needed for anyone for some reason (also check the removed code then):
    rkrisztian/contenttree@7d56ac5

    However, it wasn't without further challenges. Currently MSW does not load successfully out of the box in Vitest, which is tracked in an issue:
    angular/angular-cli#32523
    But at least I found a workaround.

    As for mocking on abstraction layer: could work too, but I'm afraid I would only repeat my previous struggles with the httpResource mocking, like losing coverage percentage, or not testing exactly what actually the app does. Signals are so behavior-defining features that if I my mocks didn't exactly follow how the app actually worked, I could have realisticity issues either breaking tests or giving me false positive results. Which means that my tests were duplicating implementation details.

    => So all in all, I think MSW is currently the biggest win here.

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 documentation

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions