Search code examples
javascriptsolid-jsvitest

SolidJS - Unhandled error when testing `data.error` in `createResource` with `vitest`


I'm trying to test a SolidJS component which shows a "Track not found" message when returning 404 from the API. The code works fine when running, but Vitest throws a Vitest caught 1 unhandled error during the test run. This might cause false positive tests. error.

Similar.tsx:

const Tracks: Component<Params> = (props) => {
  return (
    <Show
      when={!tracks.error}
      fallback={<ErrorMessage message="Track not found" />}
    >
      <div class="tracks">
        <For
          each={tracks()}
          fallback={<ErrorMessage message="No similar tracks found" />}
        >
          {(track) => (
            <Track
              name={track.name}
              artist={track.artist.name}
              image={track.image[3]['#text']}
              url={track.url}
            />
          )}
        </For>
      </div>
    </Show>
  );
};

And this is the test suite I'm running:

it('renders a "Track not found" message if API returns 404', async () => {
      vitest
        .spyOn(fetchSimilarTracks, 'default')
        .mockRejectedValueOnce(new Error('Track not found'));
      const { findByText } = renderSimilar({
        track: 'hdsauidhas',
        artist: 'hdsduhsd',
      });

      expect(await findByText('Track not found')).toBeInTheDocument();
    });

Solution

  • After looking through the docs a bit, I realized that I missed the following entry:

    data works like a normal signal getter: use data() to read the last returned value of fetchData. But it also has extra reactive properties: data.loading tells you if the fetcher has been called but not returned, and data.error tells you if the request has errored out; if so, it contains the error thrown by the fetcher. (Note: if you anticipate errors, you may want to wrap createResource in an ErrorBoundary.)

    So what I did was, instead of using <Show /> for conditionally rendering the Error Message, wrap Tracks into an <ErrorBoundary /> and provide <ErrorMessage /> as a fallback there:

    <ErrorBoundary fallback={<ErrorMessage message="Track not found" />}>
      <Tracks track={params.track} artist={params.artist} />
    </ErrorBoundary>
    

    So this is how <Tracks /> should look like:

    const Tracks: Component<Params> = (props) => {
      return (
        <div class="tracks">
          <For
            each={tracks()}
            fallback={<ErrorMessage message="No similar tracks found" />}
          >
            {(track) => (
              <Track
                name={track.name}
                artist={track.artist.name}
                image={track.image[3]['#text']}
                url={track.url}
              />
            )}
          </For>
        </div>
      );
    };