Testing functions inside stateless React component with Enzyme - reactjs

I have a stateless component:
export default function TripReportFooter(props) {
const { tripReport, user, toggleFavorite, navigation } = props;
handleShare = async slug => {
try {
const result = await Share.share({
message: `Check out this Trip Report:\n/p/${slug}/`
});
if (result.action === Share.sharedAction) {
if (result.activityType) {
} else {
// shared
}
} else if (result.action === Share.dismissedAction) {
}
} catch (error) {
alert(error.message);
}
};
handleFavorite = async id => {
const token = await AsyncStorage.getItem("token");
toggleFavorite(id, token);
};
return (
... // handleFavorite and handleShare called with TouchableOpacities.
);
}
It has two functions inside, handleShare and handleFavorite. I want to test these functions are called, and also that handleFavorite calls the prop function toggle favorite.
I tried wrapper.instance().handleFavorite(), but since it is a stateless component, it returns null.
Next someone on Stack Overflow suggested using a spy like so:
wrapper = shallow(<TripReportFooter {...props} handleFavorite={press} />);
wrapper
.find("TouchableOpacity")
.at(0)
.simulate("press");
expect(press.called).to.equal(true);
but this returned
'TypeError: Cannot read property 'equal' of undefined'.
What's the proper way to call these functions?

You first need to think about what you want to test. Is it implementation details or the interaction with your component? The latter is a much better mindset and standpoint so what I would do is to test the component from the interaction point of view.
I would (for handleShare):
Mock the Share object methods that are being called inside the share function;
Select the button I want to click/touch
Click/touch the button
Assert that the methods were called.
Now for the handleFavorite:
Mock AsyncStorage.getItem;
Create a fake toggleFavorite function that I would pass as props;
Select the button I want to click/touch
Click/touch the button
Assert my toggleFavorite function has been called
If you want to test these functions individually you would have to extract them to the outside of the component and test them individually. But I would not advise this as it is not clean and extra work.
Hope it helps!

Functions within a functional component aren't defined on the prototype or the functional component instance, you cannot directly spy on them
The solution here is to test out the internal implementation of the individual functions
For instance for handleFavourite function you can mock AsynStorage and pass on a mock function for toggleFavourite and then asset it its called on TouchableOpacity onPress simulation
You can check how to mock AsyncStore in this post:
How to test Async Storage with Jest?
const mocktToggleFavourite = jest.fn();
wrapper = shallow(<TripReportFooter {...props} toggleFavourite={mocktToggleFavourite} />);
wrapper
.find("TouchableOpacity")
.at(0)
.simulate("press");
expect(mockToggleFavourite).toHaveBeenCalled();
Similarly you can test the individual functionalities within handleShare by first mocking Share.share and then checking against each condition.For instance you can add an spy on window.alert and see if that is called
const windowSpy = jest.spyOn(window, 'alert');
wrapper = shallow(<TripReportFooter {...props} toggleFavourite={mocktToggleFavourite} />);
//Simulate event that calls handleShare
// Mock Share to give required result
expect(windowSpy).toBeCalledWith(expectedValue);

Related

How to test Component which requires hook using react testing library?

I have built a component which requires a methods and data supplied by custom hook
const MyComponent = () => {
const { data, methods } = useMyCustomHook({ defaultValues: defaultValues });
return <MyAnotherComponent data={data} label="Some Text" method={methods} />;
}
I am writing test using react testing library to test MyComponent or to be more specific to test MyAnotherComponent
Here is my testing code
test("Test MyAnotherComponent Label", () => {
const { data, methods } = useMyCustomHook({ defaultValues: defaultValues });
render(
<MyAnotherComponent
data={data}
label="Some Text"
method={methods}
/>
);
expect(screen.getByLabelText("Some Text")).toBeInTheDocument();
});
My testcase fails with an error saying Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons
I have looked up for solutions but some are too simple and some are too complex. Thanks in advance
The error is caused by the fact that you are calling the hook inside a function, not inside a React component.
Since you only want to test MyAnotherComponent, I don't see why you'd need to call the hook and not mock the data and methods directly to pass them to MyAnotherComponent because what you are currently doing is to re-write MyComponent.

how to test a method of a component in enzyme is it calls a method which is passed as prop

handleConfirm = () => {
this.props.handleCompletionDateChange();
this.setState({ showInformation: false });
};
I have above method handleConfirm in our class component DateSection
if I test handleConfirm method using below code I am getting error as
TypeError: _this.props.handleCompletionDateChange is not a function
const wrapper1 = shallow(<DateSection {...props} />);
const instance = wrapper1.instance() as any;
spyOn(instance, 'handleConfirm').and.callThrough();
expect(instance.handleConfirm()).toBe(true);
expect(instance.handleConfirm).toHaveBeenCalled();
How should pass the above function as a prop while testing,
You better not access instance() in tests. Tests become fragile(say, converting component into functional will break everything while component itself can be fine) and less reliable(if you calling methods that will never be called in real life - e.g. if that method is not called by others and not bound as event handler).\
Instead you need to find way to call that with the help of .simulate() or .props().someCabllackProp(...).
Assuming this handleConfirm is onClick handled for some <button name="confirm">:
it('calls handleCompletionDateChange', () => {
const handleCompletionDateChange = jest.fn();
const wrapper1 = shallow(<DateSection
{...props}
handleCompletionDateChange={handleCompletionDateChange} \
/>);
wrapper1.find("button[name='confirm']").simulate('click');
expect(handleCompletionDateChange).toHaveBeenCalled();
})
The same to validate this.setState({ showInformation: false }); part. We need to figure out how to validate that based on render results.
Something like
expect(wrapper1.find("[data-id='information-block']")).toHaveLength(0);

How can I test that a private function was called on a click event?

I am learning jest and I am trying to test my click events. My function that I am trying to test is private within the component.
I have tried using the spyOn() method.
This is the component calling the function
<NavbarToggler onClick={this.toggleNavbar} />
This is the function
private toggleNavbar = (): void => {
this.setState({
isOpen: !this.state.isOpen
});
}
This is the test
it('toggleNavbar is called when NavbarToggler is clicked', () => {
const wrapper = shallow(<NavBar />);
const instance = wrapper.instance();
jest.spyOn(instance, 'toggleNavbar');
wrapper.find(NavbarToggler).simulate('click');
expect(instance.toggleNavbar).toEqual(true);
});
I am currently getting an error on jest.spyOn saying toggleNavbar is not assignable to parameter
More often than not you shouldn't need to do that as you'd be testing implementation detail of a component - it'd make your test fragile. If your private method sets a state, then the state most likely changes the output of what component renders. You should assert that after it's triggered (via a click for example), the output of render function contains that change.
Assuming you're using enzyme
// expect(wrapper.find(Navbar)).not.toExist(); // enzyme-matchers provides `toExists()`
wrapper.find(NavbarToggler).simulate('click');
expect(wrapper.find(Navbar)).toExist(); // enzyme-matchers provides `toExists()`
To answer your question - if you're using enzyme you can inspect component's state. Another super dirty solution would be instance.toggleNavbar = jest.fn(), though as said before, you should never need to do that.

Jest mocking with react calling actual event handler after calling mock function

I have this component test
const component = mount(<MemoryRouter><Login/></MemoryRouter>);
const emailField = component.find('input[type="email"]');
const passwordField = component.find('input[type="password"]');
const mockedSubmitFunction=jest.fn();
component.find(Login).instance().onSubmit=mockedSubmitFunction;
emailField.simulate('change', { target: { value: testEmail } });
passwordField.simulate('change', { target: { value: testPassword } });
component.find('form').simulate('submit');
expect(mockedSubmitFunction).toBeCalled();
and in the component i have
in constructor :-
this.onSubmit = this.onSubmit.bind(this);
and the eventhandler
onSubmit(event) {
event.preventDefault();
when i put a breakpoint in onSubmit it is coming to the component function after executing the mocked onSubmit, why is this happening.
I assumed it will only call the mocked onSubmit.
What am I doing differently?
CodeSandbox :https://codesandbox.io/s/q95lv7vlrw
But the sandbox is showing Could not find module in path: 'object-inspect/util.inspect' relative to '/node_modules/object-inspect/index.js' for some reason, which is unrelated i guess
So you got function mocked, but actual onSubmit is called. Instead if you want to call only mocked fn you have to provide it (as a prop in your test spec for example).
const mockedSubmitFunction = jest.fn(event => {
console.log("Mocked function");
});
const component = mount(
<MemoryRouter>
<Login login={mockedSubmitFunction} />
</MemoryRouter>
);
I updated sandbox for you.
You can additionally check this explained example on form testing.
Update: i suppose that the actual problem OP has was that mock function was firing, but it was copied to instance, thus expect...toBeCalled() fails (actual mockedFn was not called). You can avoid these problems by passing mocked function as a prop, spying on a function, etc.

How to test properties and functions on a React component?

I've tried everything with enzyme, however, I can't find the correct way of testing these properties below. Keep in mind that this component is wrapped in a dummy Provider component so that I can pass the necessary props (i.e. Store) down for mounting purposes.
1) After mounting, a property is set on the instance (e.g. this.property)
2) An event listener has been added
3) On the event listener, someFunction is being called
class SampleComponent extends Component {
componentDidMount() {
this.property = 'property';
window.addEventListener('scroll', this.someFunction, true);
}
someFunction = () => {
return 'hello';
};
render() {
return <h1>Sample</h1>;
}
}
export default EvalueeExposureList;
Ok, I have updated my answer based on discussion with OP. The component under test has a redux provider and connected component as child therefore we are opting for the usage of enzymes shallow API.
In regards to tracking and testing the addEventListener you can use the sinon library to create a spy, which temporarily "replaces" the window.addEventListener. This grants you access to the call count as well as the arguments it was called with.
Using enzyme and mocha I created the following tests which were passing for me. The first two test covers all your cases above and for good measure I added another on how to test the output of the someFunction.
import React from 'react';
import { expect } from 'chai';
import sinon from 'sinon';
import { shallow } from 'enzyme';
// Under test.
import SampleComponent from './SampleComponent';
describe('SampleComponent', () => {
let addEventListenerSpy;
beforeEach(() => {
// This replaces the window.addEventListener with our spy.
addEventListenerSpy = sinon.spy(window, 'addEventListener');
});
afterEach(() => {
// Restore the original function.
window.addEventListener.restore();
});
// This asserts your No 1.
it(`should set the property`, () => {
const wrapper = shallow(<SampleComponent />);
wrapper.instance().componentDidMount(); // call it manually
expect(wrapper.instance().property).equal('property');
});
// This asserts your No 2 and No 3. We know that by having
// passed the someFunction as an argument to the event listener
// we can trust that it is called. There is no need for us
// to test the addEventListener API itself.
it(`should add a "scroll" event listener`, () => {
const wrapper = shallow(<SampleComponent />);
wrapper.instance().componentDidMount(); // call it manually
expect(addEventListenerSpy.callCount).equal(1);
expect(addEventListenerSpy.args[0][0]).equal('scroll');
expect(addEventListenerSpy.args[0][1]).equal(wrapper.instance().someFunction);
expect(addEventListenerSpy.args[0][2]).true;
});
it(`should return the expected output for the someFunction`, () => {
const wrapper = mount(<SampleComponent />);
expect(wrapper.instance().someFunction()).equal('hello');
});
});
It may be worth noting that I run my tests on node, but I have a jsdom setup in my mocha configuration, which is probably the candidate responsible for creating the window.addEventListener in for use in my test environment. Are you running your tests via the browser or node? If node you may need to do something similar to me.

Resources