Mocking callback ref in Jest - reactjs

I have a component that renders a third party component. When rendering, it stores this component's ref locally in localApi. When the button is clicked it calls the exportData function which returns some data in a callback.
const MyComponent = () => {
let localApi = React.useRef() as any;
const submit = () => {
localApi.exportData((data) => {
// do something with data
})
}
return (
<>
<ThirdParty ref={(api) => localApi = api} fullVersion={true}/>
<button onSubmit={submit}>Submit</button>
</>
)
}
The problem I'm facing is mocking this ThirdParty component's ref in Jest. Since my mock isn't working, upon simulating a clickon the button throws exportData is not defined error. I've tried the following to no avail:
jest.mock("third-party", () => ({
default: (props: any) => {
// tslint:disable-next-line: no-shadowed-variable
const React = require("react");
console.log(props); // this only returns the `fullVersion = true` when
const MockThirdParty: React.FC = () => {
const exportData = (callBack) => {
callBack("Mock data");
}
return <div>Third Party Component</div>;
}
return <MockThirdParty/>;
},
__esModule: true,
}));
How can I go about mocking it properly?

Related

How to test a component that is conditionally rendered based on a hook value?

I am working on a React Native application and am very new to testing. I am trying to mock a hook that returns a true or false boolean based on the current user state. I need to mock the return value of the authState variable, and based on that, I should check if the component is rendered or not. But the jest mock is returning the same value only
useAuth.ts
export const useAuthState = () => {
const [authState, setAuthState] = useState<AuthState>();
useEffect(() => {
return authentication.subscribe(setAuthState);
}, []);
return authState;
};
MyComponent.tsx
export const MyComponent = () => {
const authState = useAuthState();
if (!authState) {
return null;
}
return <AnotherComponent />
}
MyComponent.test.tsx
import { MyComponent } from "./MyComponent"
jest.mock('../use-auth-state', () => {
return {
useAuthState: () => false,
};
});
const TestComponent = () => <MyComponent />
describe('MyComponent', () => {
it('Should return null if the authState is null', () => {
let testRenderer: ReactTestRenderer;
act(() => {
testRenderer = create(<TestComponent />);
});
const testInstance = testRenderer.getInstance();
expect(testInstance).toBeNull()
})
})
This is working fine. But, I am not able to mock useAuthState to be true as this false test case is failing. Am I doing it right? I feel like I am messing up something.
You want to change how useAuthState is mocked between tests, right? You can set your mock up as a spy instead and change the mock implementation between tests.
It's also a little more ergonomic to use the render method from react-testing-library. The easiest way would be to give your component a test ID and query for it. Something like the below
import { MyComponent } from "./MyComponent"
import * as useAuthState from '../use-auth-state';
const authStateSpy = jest.spyOn(useAuthState, 'default');
describe('MyComponent', () => {
it('Should return null if the authState is null', () => {
// you can use .mockImplementation at any time to change the mock behavior
authStateSpy.mockImplementation(() => false);
const { queryByTestId } = render(<MyComponent />;
expect(queryByTestId('testID')).toBeNull();
})

Mock a module that returns a function and test that this function was called

I want to test a React component that, internally, uses a custom hook (Jest used). I successfully mock this hook but I can't find a way to test the calls on the functions that this hook returns.
Mocked hook
const useAutocomplete = () => {
return {
setQuery: () => {}
}
}
React component
import useAutocomplete from "#/hooks/useAutocomplete";
const MyComponent = () => {
const { setQuery } = useAutocomplete();
useEffect(() => {
setQuery({});
}, [])
...
}
Test
jest.mock("#/hooks/useAutocomplete");
it("sets the query with an empty object", () => {
render(<MyComponent />);
// I want to check the calls to setQuery here
// e.g. mockedSetQuery.mock.calls
});
CURRENT SOLUTION
I currently made the useAutocomplete hook an external dependency:
import useAutocomplete from "#/hooks/useAutocomplete";
const MyComponent = ({ autocompleteHook }) => {
const { setQuery } = autocompleteHook();
useEffect(() => {
setQuery({});
}, [])
...
}
MyConsole.defaultProps = {
autocompleteHook: useAutocomplete
}
And then I test like this:
const mockedSetQuery = jest.fn(() => {});
const useAutocomplete = () => ({
setQuery: mockedSetQuery,
});
it("Has access to mockedSetQuery", () => {
render(<MyComponent autocompleteHook={useAutocomplete} />);
// Do something
expect(mockedSetQuery.mock.calls.length).toBe(1);
})
You can mock the useAutocomplete's setQuery method to validate if it's invoked.
jest.mock("#/hooks/useAutocomplete");
it("sets the query with an empty object", () => {
const useAutocompleteMock = jest.requireMock("#/hooks/useAutocomplete");
const setQueryMock = jest.fn();
useAutocompleteMock.setQuery = setQueryMock;
render(<MyComponent />);
// The mock function is called twice
expect(setQueryMock.mock.calls.length).toBe(1);
});

Test custom hook in ReactJS

I have this hook that should trigger beforeunload event when the compoenent is mounted and unmounted.
const UseHook = (fns: (e) => void) => {
const cb = useRef(fns);
useEffect(() => {
cb.current = fn;
}, [fn]);
useEffect(() => {
const onUnloadFN = (args: BeforeUnloadEvent) => cb.current?.(args);
window.addEventListener('beforeunload', onUnloadFN);
return () => {
window.removeEventListener('beforeunload', onUnloadFN);
};
}, []);
};
Now I want to test the hook using jest and enzyme:
import { mount } from 'enzyme';
import React from 'react';
const HookWrapper = () => {
useHook((e) => {
e.preventDefault();
e.returnValue = '';
});
return <div>component</div>;
};
describe('useHook', () => {
const location: Location = window.location;
delete window.location;
const mockPageReloading = jest.fn();
window.location = {
...location,
reload: mockPageReloading,
};
it('should mount', () => {
const mockedOnload = jest.fn();
window.addEventListener = jest.fn((event) => {
if (event === 'beforeunload') {
mockedOnload();
}
});
const wrapper = mount(<HookWrapper />);
expect(mockedOnload).toHaveBeenCalledTimes(1);
jest.restoreAllMocks();
console.log(wrapper.debug());
});
it('should unmount', () => {
const mockedOnload = jest.fn();
window.removeEventListener = jest.fn((event) => {
if (event === 'beforeunload') {
mockedOnload();
}
});
const wrapper = mount(<HookWrapper />);
wrapper.unmount();
expect(mockedOnload).toHaveBeenCalledTimes(1);
});
});
The first test pass, but the second retrieve that the event listener wasn't call on unmount (it was called 0 times).
Who can help with this?
Basically I want to test if the event was triggered on mount and also on unmount.
PS: this hook is also used to detect when user reload the page. If somebody has other idea how to test this hook, please let me know.

Mocked useHistory is not called in async event handler

Summary
I'm writing test code for my react app, but somehow, it always fails.
My app code is very simple, there is only one button, and if it's clicked, a function handleSubmit is fired.
What the handler does are
Fetching data from backend(This is async function)
Move to /complete page.
What I did
I mocked the function fetching data from API in test code
I mocked the useHistory in test code
Note
I realized that if the line that is fetching data from API is commented out, the test will pass.
Code
My main app code
import { useFetchDataFromAPI } from '#/usecase/useFetchDataFromAPI';
:
const { fetchDataFromAPI } = useFetchDataFromAPI();
:
const handleSubmit = async () => {
// If the line below is not commented out, test will fail
// const { id } = await fetchDataFromAPI();
history.push(`/complete`);
};
return (
<>
<button onClick={handleSubmit}>Button</button>
</>
My test code
:
jest.mock('#/usecase/useFetchDataFromAPI', () => ({
useFetchDataFromAPI: () => {
return { fetchDataFromAPI: jest.fn((): number => {
return 1;
})}
}
}));
const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom') as any,
useHistory: () => ({
push: mockHistoryPush,
}),
}));
:
const renderApplicationWithRouterHistory = () => {
const history = createMemoryHistory();
const wrapper = render(
<Router history={history}>
<Application />
</Router>
);
return { ...wrapper, history };
};
:
describe('Test onClick handler', async () => {
test('Submit', () => {
const { getByText, getByRole } = renderApplication();
const elementSubmit = getByText('Button');
expect(elementSubmit).toBeInTheDocument();
fireEvent.click(elementSubmit);
expect(mockHistoryPush).toHaveBeenCalled();
});
});
Your event handler is called on button click, but because it is asynchronous, its result is not evaluated until after your test runs. In this particular case, you don't need the async behavior, so just use:
const handleSubmit = () => {
history.push(`/complete`)
}
testing-library provides a method waitFor for this if your handler did need to await something:
await waitFor(() => expect(mockHistoryPush).toHaveBeenCalled())
Though another simple way is to simply await a promise in your test so that the expectation is delayed by a tick:
fireEvent.click(elementSubmit);
await Promise.resolve();
expect(mockHistoryPush).toHaveBeenCalled();

Unable to spy on third party library function in test

I have a component that uses a third party library. When the button is clicked, it triggers the library's api.. gets the data from its callback and calls the this.props.SaveData with the returned data.
const Comp = () => {
let editorApi: any;
const triggerSave = () => {
// call to third party component api
editorApi.export((data) => {
this.props.SaveData(data.values);
})
}
return (
<>
<ThirdPartyEditor ref={(editor: any) => editorApi = editor} />
<button onClick={triggerSave}>Save Data</button>
</>
)
}
Since I'm testing the Comp component, I mocked third-party-editor in my test. But now when I try to test that this.props.SaveData was called I'm getting this error:
Cannot spy the export property because it is not a function; undefined given instead
Test:
jest.mock("third-party-editor", () => ({
default: () => "MyEditor"
}))
test("it calls SaveData when button is clicked", () => {
const editor = jest.spyOn(ThirdPartyEditor, "export").mockImplementation((data) => );
const { container, queryByText } = render(<Comp {...props} />);
act(() => fireEvent.submit(queryByText("Save Data")));
// fails to find export
})
EDIT: Different mocking approach
jest.mock("third-party-editor", () => ({
default: () => {
return class Mocked {
public editorApi = jest.fn();
public export = jest.fn();
public render() {
return "Editor";
}
};
},
}));
Returns the error TypeError: Cannot read property 'export' of undefined

Resources