Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot test hook raises an error #960

Open
matiasgarcia opened this issue Apr 25, 2023 · 1 comment
Open

Cannot test hook raises an error #960

matiasgarcia opened this issue Apr 25, 2023 · 1 comment
Labels
question Further information is requested

Comments

@matiasgarcia
Copy link

Dependencies:

  • react 17.0.2
  • @testing-library/react-hooks@7.0.1
  • @testing-library/react@12.0.0

I have the following hook

function usePlan(): { loading: boolean, plan?: Plan, error?: Error } {
  const [loading, setLoading] = useState(true);
  const [plan, setPlan] = useState<Plan>();
  const [error, setError] = useState<Error>();

  useEffect(() => {
    const tryFetchSettlementFees = async () => {
      setLoading(true);

      try {
        const currentPlan = await client.getStorePlan();
        setPlan(currentPlan);
        setLoading(false);
        setError(undefined);
      } catch (error: any) {
        setError(error);
        setLoading(false);
        if(!axios.isAxiosError(error)) throw error;
      }
    };

    tryFetchSettlementFees();
  }, [setLoading, setPlan]);

  return {
    loading,
    error,
    plan,
  };
}

And I am trying to test the error is raised

	it.only('throws when effect fails due to something not request related', () => {
		const expectedError = new Error('expected error');
		client.getStorePlan = jest.fn().mockRejectedValueOnce(expectedError);

		let caughtError = null;
		class ErrorBoundary extends React.Component {
			constructor(props) {
				super(props)
				this.state = { hasError: false }
			}
	
			componentDidCatch(error) {
				this.setState({ hasError: true })
				caughtError = error
			}
	
			render() {
				return !this.state.hasError && this.props.children
			}
		}
	
		const wrapper = ({ children }) => <ErrorBoundary>{children}</ErrorBoundary>

		const vals = renderHook(() => usePlan(), { wrapper });

		expect(caughtError).toEqual(expectedError);
	});

Also tried

const expectedError = new Error('expected error');
client.getStorePlan = jest.fn().mockRejectedValueOnce(expectedError);
expect(() => renderHook(() => usePlan())).toThrow(expectedError);

Also tried

const expectedError = new Error('expected error');
client.getStorePlan = jest.fn().mockRejectedValueOnce(expectedError);
const { current } = renderHook(() => usePlan());
expect(current.error?.message).toEqual(...);

But in all of them I cannot seem to get the error I expect, which is the useEffect throwing the error. However, I get on jest an unhandledPromiseRejection so I might be doing something wrong:

node:internal/process/promises:265
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Error: expected error".] {
  code: 'ERR_UNHANDLED_REJECTION'
}
@matiasgarcia matiasgarcia added the question Further information is requested label Apr 25, 2023
@mpeyper
Copy link
Member

mpeyper commented Apr 26, 2023

So what is happening here is that your promise (tryFetchSettlementFees() will return a promise) is rejecting, but nothing is catching it so there is no way to test for it. Try changing the call in your effect to this to see what I mean:

tryFetchSettlementFees().catch((err) => console.error(err));

There isn't a way (that I know of) to capture the request promise into the effect itself without storing the result/error into state like you are doing, so you likely just want to move the throw outside of the effect, something like:

function usePlan(): { loading: boolean, plan?: Plan, error?: Error } {
  const [loading, setLoading] = useState(true);
  const [plan, setPlan] = useState<Plan>();
  const [error, setError] = useState<Error>();

  useEffect(() => {
    const tryFetchSettlementFees = async () => {
      setLoading(true);

      try {
        const currentPlan = await client.getStorePlan();
        setPlan(currentPlan);
        setLoading(false);
        setError(undefined);
      } catch (error: any) {
        setError(error);
        setLoading(false);
      }
    };

    tryFetchSettlementFees();
  }, [setLoading, setPlan]);

  if(error && !axios.isAxiosError(error)) throw error;

  return {
    loading,
    error,
    plan,
  };
}

(note this is entirely untested)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants