Unable to spy on third party library function in test - reactjs

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

Related

How can I mock an imported React hook/module and test that it's being called properly on different test cases using Jest

I need to test the following component that consumes a custom hook of mine.
import { useMyHook } from 'hooks/useMyHook';
const MyComponent = () => {
const myHookObj = useMyHook();
const handler = () => {
myHookObj.myMethod(someValue)
}
return(
<button onClick={handler}>MyButton</button>
);
};
This is my test file:
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: () => {
return {
myMethod: jest.fn(),
};
},
};
});
describe('<MyComponent />', () => {
it('calls the hook method when button is clicked', async () => {
render(<MyComponent {...props} />);
const button = screen.getByText('MyButton');
userEvent.click(button);
// Here I need to check that the `useMyHook.method`
// was called with some `value`
// How can I do this?
});
});
I need to check that the useMyHook.method was called with some value.
I also want to test it from multiple it cases and it might be called with different values on each test.
How can I do this?
This is how I was able to do it:
import { useMyHook } from 'hooks/useMyHook';
// Mock custom hook that it's used by the component
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: jest.fn(),
};
});
// Mock the implementation of the `myMethod` method of the hook
// that is used by the Component
const myMethod = jest.fn();
(useMyHook as ReturnType<typeof jest.fn>).mockImplementation(() => {
return {
myMethod: myMethod,
};
});
// Reset mock state before each test
// Note: is needs to reset the mock call count
beforeEach(() => {
myMethod.mockReset();
});
Then, on the it clauses, I'm able to:
it (`does whatever`, async () => {
expect(myMethod).toHaveBeenCalledTimes(1);
expect(myMethod).toHaveBeenLastCalledWith(someValue);
});

How to test with Jest whether action created by action creator (and not passed as prop) fires?

I try to test whether a given action is really fired in a component. It could be easy if the callback with action was delivered as a prop, but it's not clear for me how to do it in my case. Here is the component:
export const Modal = (props: appendProps): JSX.Element => {
const { items, activeScope } = props;
const primary = items[0] === activeScope;
const { closeInput, appendItem } = useDispatchAction();
{...}
<Button
variant="contained"
size="large"
className="modal-content__button"
color="secondary"
onClick={() => {
closeInput();
}}
>
Zamknij
</Button>
useDispatchAction is below:
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../redux';
const useDispatchAction = () => {
const dispatch = useDispatch();
return bindActionCreators(actionCreators, dispatch);
};
export default useDispatchAction;
And test itself below
describe('Test button actions', () => {
const closeInput = jest.fn();
jest.mock('../../src/hooks/useDispatchAction', () => ({closeInput}));
beforeEach(() => {
render(<Modal />);
});
afterEach(() => cleanup());
test('component fires closeInput', () => {
const closeButton = screen.getByRole('button', { name: 'Zamknij' });
expect(closeButton).toBeInTheDocument();
userEvent.click(closeButton);
expect(closeInput).toBeCalled();
});
});
but this keep telling me that function has been called 0 times and not 1 at least
When you jest.mock, you get a function that returns undefined by default. So, if useDispatchAction returns undefined, you can't go const { closeInput, appendItem } = useDispatchAction();, because you cannot destructure from undefined.
However, you can supply a second argument to jest.mock:
const closeInput = jest.fn()
jest.mock('../../src/hooks/useDispatchAction', () => ({ closeInput }))
Now you've mocked useDispatchAction to return something that looks more like your original function, except that the action returns something you can assert was called.
You can test it like this:
expect(closeInput).toHaveBeenCalled()
I think this should work, but I haven't tested this particular code.
What finally works is:
const actions = {
closeInput: jest.fn(),
appendItem: jest.fn(),
};
jest.mock('../../src/hooks/useDispatchAction', () => () => actions);
describe('Given Modal component', () => {
describe('when "Zamknij" button is clicked', () => {
it('should call closeInput function', async () => {
actions.closeInput.mockClear();
const { findByText } = render(<Modal />);
const closeButton = await findByText('Zamknij');
fireEvent.click(closeButton);
expect(actions.closeInput).toHaveBeenCalled();
});
});
});

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();

Mocking callback ref in Jest

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?

React container component's method test with jest

I want to write unit test for React component's method.
The component's code is
export class LoginForm extends Component {
constructor() {
super();
this.tryLoginProp = this.tryLoginProp.bind(this)
}
render() {
return (
<div className="login-form">
<div className="form-input">
<CustomButton label="Log in"
class="login-button"
action={this.tryLoginProp}
id="login-button-loginform"
renderSpinner={this.props.renderSpinner}
/>
</div>
</div>
)
}
tryLoginProp () {
if (!this.props.renderSpinner) {
this.props.tryLoginProp()
}
}
}
const mapStateToProps = (state) => {
return {
login: state.login,
renderSpinner: state.layout.renderSpinner
};
};
const mapDispatchToProps = (dispatch) => ({
tryLoginProp: () => {
dispatch(tryLogin())
}
});
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
I want to write unit test for tryLoginProp method, but I am not getting how to mock this.props.tryLoginProp function and pass the test case.
Current unit test is as follows:
describe('<LoginForm />', () => {
const initialState = {renderSpinner: false};
let wrapper;
beforeEach(() => {
wrapper = mount(<LoginForm {...initialState}/>);
});
it('renders without expolding', () => {
expect(wrapper.length).toEqual(1);
});
it('tryLoginProp should dispatch an action', () => {
expect(wrapper.tryLoginProp()). //test tryLoginProp method.
})
});
Please help me to write proper test case for this method.
Thanks
You can use wrapper.instance().tryLoginProp() to call the method...like this I think without testing it
it('tryLoginProp should dispatch an action', () => {
const mockedTryLoginProp = jest.fn();
const wrapper = shallow(
<LoginForm
tryLoginProp={mockedTryLoginProp}
renderSpinner={false}
/>
);
wrapper.instance().tryLoginProp();
expect(mockedTryLoginProp).toHaveBeenCalled();
})
On a side note, you may consider naming the internal function differently than the one being passed in to avoid confusion

Resources