Jest & react-testing-library - add test for conditionally rendered children - reactjs

I have a component for which I have this test
test('Some Test', () => {
const { getByTestId } = render(<SomeComponent>Some String</SomeComponent>);
const componentNode = getByTestId('primary');
expect(componentNode).toEqual('Some String');
});
This component accepts an optional prop loading (boolean) which is by default false. When loading is true, a spinner is rendered instead of any children passed to this component.
I want to create a test (and probably change the existing one) for when I pass the loading prop (to test that the component changes state). How can I do it the best way?

You need to mock loading prop in test and to pass it to your component. I guess it is true or false. But since in your test code you do not forward any loading prop, I assume it is then undefined inside component.
In case of false value, you need to verify that those conditionally rendered elements are not rendered. In case when you want to test that some element is not rendered, React Testing Library offers API just for that. From the docs:
queryBy...: Returns the first matching node for a query, and return
null if no elements match. This is useful for asserting an element
that is not present.
So inside your test, you need something like: expect(queryBy*(...)).not.toBeInTheDocument();
And for case when you forward true as value for loading prop, you need to verify that your elements are rendered using the await findBy*() API. You can take a look for more in link provided above.

You need to pass loading flag value as a prop and then assert based on props passed.
Ex:
test('Some Test', () => {
render(<SomeComponent loading={true}>Loading</SomeComponent>);
const loading = screen.queryByText('Loading')
expect(loading).toBeInTheDocument();
});
test('Some Test', () => {
render(<SomeComponent loading={false}>Hello</SomeComponent>);
const hello = screen.queryByText('Hello')
expect(hello).toBeInTheDocument();
});
Also you can refers to https://kentcdodds.com/blog/common-mistakes-with-react-testing-library which suggest to use screen for querying.

Related

Testing in React: How to set a state from testfile?

I want to test a component and it contains conditional rendering. So if a state is true, the JSX-HTML should render, if false not.
If I want to test this by using Jest, Enyzme, React-Testing-Library how should I proceed?
I have a component, its implementation details do not matter for this topic. Just be aware, that it contains
const [isBlocked, setIsBlocked] = useState(false);
And for my test, I want to set this to true.
describe('rendering DiffWindow', () => {
it("renders HeaderBar components without crash", () => {
const wrapper = shallow( < HeaderBar /> );
// HOW TO SET HERE THE STATE?
expect(wrapper.find('.header-is-there').exists()).toBeTruthy();
});
});
Despite the implementation details, there are two situations in your problem:
1. State is managed by the component itself
For boolean state, the initial value is false, followed by an API call to change its value to true, or an event handler triggered by user interaction.
You need to set up a mock HTTP server to response the API call, or use fireEvent[eventName] to simulate user interaction. Assert component view changes when state changes.
2. State is managed by the parent component
This case is mentioned by #blessenm. Generally, the state will have to be initialized by a prop passed from a parent component.
You can pass the prop with different values to change the view of the component.

Not simulating 'change' using enzyme

I have a Reactjs component and it has one button and a date-range picker.
I want to simulate onclick and onchange events of button and picker respectively.
I'm able to simulate onclick of the button. but on change of datepicker is not working
I have tried this
headerComponent.find(`#prev_button`).at(1).simulate("click");
headerComponent.find(`#dropdown`).at(1).simulate("change", { value: "t" });
please see this sandbox click here for full code and test file
Based on Enzyme documentation you make a mistake on your second argument on simulate function.
To simulate changes on the input, you should change it like this :
headerComponent.find(`#dropdown`).at(1).simulate("change", { target: { value: "t" } });
Testing with enzyme is tricky. You should try not to test dependencies because you trust those are already tested. Having said that, you could shallow render instead of mounting and look for the RangePicker component in the shallow tree, get the handler you are passing in the onChange prop and call it manually, then check the callback prop you pass to your component is called with the expected value.
describe.only("test", () => {
it("should render", () => {
const callBackToSetDates = jest.fn();
const callBackToSetFilter = jest.fn();
const wrapper = shallow(
<Header
{...headerProps1}
callBackToSetDates={callBackToSetDates}
callBackToSetFilter={callBackToSetFilter}
/>
);
const rangePickerOnChange = wrapper.find("RangePicker").prop("onChange");
rangePickerOnChange("someValue");
expect(callBackToSetDates).toHaveBeenCalledWith("someValue");
});
});
the purpose is to test only the logic you add inside your component, i.e., you transform the value you get from the RangePicker to something else
<RangePicker
...
onChange={(value) => {
callBackToSetDates(`I'm transforming ${value}`);
}}
/>
and in your test
rangePickerOnChange("someValue");
expect(callBackToSetDates).toHaveBeenCalledWith("I'm transforming someValue");
you can see it working here https://codesandbox.io/s/cool-rosalind-uec6t?file=/src/tests/index.test.js
If you really want to keep testing what the actual user sees, you'll need to fire the events that the user does when using the component. In this case: you need to click the input, look for a date, click it, then click another date to completely fire the onChange event of the RangePicker component. You might look at how antd test it and copy the necessary jest configuration they have to mock some DOM APIs

Testing onChange event with Jest

According to my code coverage, I need to test the function called in my onChange event. This is actually where I update my functional component's state using the useState hook.
Here is my component :
const Component:React.FC<{}> = () => {
const {value, setState} = useState('');
return(
<View>
<CustomComponent
onChange={(value) => setState(value)}
/>
</View>
)
}
The customComponent is a component derived from the React Input component.
When text is changed inside of it, it calls the onChange function passed as its prop with the text. This is how it comes up to the parent Component which sets the value of the text input in its state as displayed above.
My code coverage returns this analysis :
onChange={//(value) => setState(value)//}
Where the code between the // has to be covered. I don't really understand how I can cover this. First thought was use mock functions, but I can't seem to find how to mock it to the onChange event since I don't pass anything as prop to the main Component.
After a few tests, I finally understood that the coverage wasn't asking for the actual test of the onChange function but actually the value that is evaluated. Therefore, here is what I am doing:
Fetching the TextInput Child component
Changing its Text
Evaluating what it renders
I am using #testing-library/react-native here because it makes selecting tree components easier with the use of accessibilityLabel for example (It actually made me understand the importance of that prop).
Here is what a test looks like:
describe('Testing useState functions', () => {
test('test', () => {
//Rendering the component and its tree
const { container, getByLabelText } = render(<SignupView />);
//Extracting the child, username_input component with his accessibilityLabel
const username_input = getByLabelText('username_input');
const email_input = getByLabelText('email_input');
//Fire a native changeText event with a specific value
fireEvent.changeText(username_input, 'doe');
fireEvent.changeText(email_input, 'doe#joe.com');
//Checking the rendered value
expect(username_input.props.value).toEqual('doe');
expect(email_input.props.value).toEqual('doe#joe.com');
});
});

Test that React prop method has not been called with Jest

How do I test a prop(callback) function that is called conditionally
I have a component which passes a prop function to its child on a condition like:
if(this.props.myFun) {
this.props.myFun();
}
to test this, I have two cases here:
1. Where the prop is passed to the component
<ChildComp myFun={value => value } /> and I can test it from the child-
const comp = mountWithIntl(<ChildComp myFun={value => value } />);
expect(comp.instance().props.myFun).toHaveBeenCalled();
Where the prop is not passed: I trying like
const comp = mountWithIntl(<MyComp />);
expect(comp.instance().props.myFun).not.toHaveBeenCalled();
But, Since If I mock the prop while mounting the component, the
method will be called. But how do I test the undefined or unmocked
prop?
If I dont pass the prop and do:
expect(comp.instance().props.myFun).not.toHaveBeenCalled();
I will get:
jest.fn() value must be a mock function or spy
which I cant do as per my code
please help
I don't think you can test that a not passed function does not get called. What you can do to test if it is get rendered without that function. That should be enough

Set Props from External Source

When a UI event occurs, my top-level React component receives data to use as props from an external object. I'd like to know the correct way to update the component's props to use for the next render.
It seems like one of the component lifecycle methods should handle this, but that doesn't seem to be the case.
The code below shows what I've tried:
• Root.update: Custom method invoked externally once the data is ready. Both of the two techniques shown do work.
• Root.getDefaultProps: Used to retrieve props for first render.
• Root.render: This also works, but is redundant on first render.
• Root.componentWillUpdate: Does not work but seems like it should.
• Root.componentWillReceiveProps: Wouldn't make sense for this to work; props aren't received from a React component.
var Root = React.createClass({
update: function() {
this.setProps(Obj.data); // works but setProps is deprecated
this.props = Obj.data; // always works
},
getDefaultProps: function() {
return Obj.pageload(); // works on first render
},
componentWillUpdate: function() {
this.props = Obj.data. // does not work
this.setProps(Obj.data); // infinite loop
},
componentWillReceiveProps: function() {
this.props = Obj.data; // does not work
},
render: function() {
this.props = Obj.data; // works but is redundant
// ...
},
});
At some point you must be calling React.render to render that initial root component with your props.
To update the component with new props simply call React.render again for the same component on the same Dom element with the new props.
This won't re-mount the component but will in fact simply send the mounted instance your new props and cause it to re-render with them.
If you want to control what happens when your component receives props then take a look at the component lifecycle method componentWillReceiveProps.

Resources