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

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.

Related

Testing React component that observes state

Say I have a simple React functional component that largely just observes changes in context and renders some view of the state from the context.
export default function Observer(props) {
// I have a separate dispatch context for performance. This component doesn't dispatch
const state = useContext(SomeContext);
return (
<div>Foo is now {state.foo}</div>
)
}
For testing under jest/react testing library, I was imagining a few possibilities:
Wire up a wrapper for the context that just instantiates different
states for each test. This seems like it would be easy and straightforward. Is this
the typical pattern?
Instantiate each test with real initial state.
Then somehow change state and expect the component to update.
Perhaps using dispatch from the test code somehow. Most examples I see fire events, but this
component doesn't dispatch anything, it just observes.
Render a larger part of the app, with other components that update the state.
This seems like a bad choice for a unit test of a small, simple
component.
The first option is the way to go. As you already mentioned, the component in question is a consumer of the context. So, if it is provided with some value for context, it will most certainly use it.
You can create a simple function to pass different context values, which is provided via provider and is rendered. Then you assert what happens,
const renderComponent = (contextValue) => {
render(
<SomeContextProvider value={contextValue}>
<Observer />
</SomeContextProvider>
);
};
test('my test case name', () => {
render({foo: abc});
expect(screen.getByText('Foo is now abc')).toBeInTheDocument();
})
Some good reading here https://testing-library.com/docs/example-react-context/

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

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.

How should I mock state in a functional stateful component with state hooks when testing?

If I have a stateful functional component that uses state hooks, how can I manipulate the state from outside the component? I want to be able change the state in my tests so that I can test how the dom changes.
I want to be able to change the value of something like this from outside of the component:
const [sent, setSent] = useState(false);
I would not test the internal state of the component but rather what this state might represent. The usage of the state is an implementation detail and you should test if the specification is implemented correctly and not how it is implemented.
In your case I would use the following approach:
Call the functionality that should set the sent state (button click,
form submit)
Test if the sent status is handled correctly in your
component (a success message is shown?)
Testing loading states of api calls can be achieved with mocks. If you don't use some fancy library but just do normal await apiCall() then you can use following approach:
Mock your api call (you probably already doing it)
Return a Promise() from the mock that will not be resolved
Example with enzyme:
import { apiCall } from '../api';
jest.mock('../api');
// ...
it('Contains a <Loading /> on loading', () => {
// The promise won't be resolved so the loading state will persist
apiCall.mockReturnValue(new Promise(() => null));
// await act... and wrapper.update() might not be needed
// depending on your implementation
await act(async () => {
wrapper.find(SubmitButton).simulate('click');
});
wrapper.update();
expect(wrapper.find(Loading)).toHaveLength(1);
});

Can't perform a React State update on unMounted child component?

Am getting this warning:
Can't perform a React state update on unmounted component. This is a no-op...
It results from a child component and I can't figure out how to make it go away.
Please note that I have read many other posts about why this happens, and understand the basic issue. However, most solutions suggest cancelling subscriptions in a componentWillUnmount style function (I'm using react hooks)
I don't know if this points to some larger fundamental misunderstanding I have of React,but here is essentially what i have:
import React, { useEffect, useRef } from 'react';
import Picker from 'emoji-picker-react';
const MyTextarea = (props) => {
const onClick = (event, emojiObject) => {
//do stuff...
}
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
});
useEffect(() => {
return () => {
console.log('will unmount');
isMountedRef.current = false;
}
});
return (
<div>
<textarea></textarea>
{ isMountedRef.current ? (
<Picker onEmojiClick={onClick}/>
):null
}
</div>
);
};
export default MyTextarea;
(tl;dr) Please note:
MyTextarea component has a parent component which is only rendered on a certain route.
Theres also a Menu component that once clicked, changes the route and depending on the situation will either show MyTextarea's parent component or show another component.
This warning happens once I click the Menu to switch off MyTextarea's parent component.
More Context
Other answers on StackOverflow suggest making changes to prevent state updates when a component isn't mounted. In my situation, I cannot do that because I didn't design the Picker component (rendered by MyTextarea). The Warning originates from this <Picker onEmojiClick={onClick}> line but I wouldn't want to modify this off-the-shelf component.
That's explains my attempt to either render the component or not based on the isMountedRef. However this doesn't work either. What happens is the component is either rendered if i set useRef(true), or it's never rendered at all if i set useRef(null) as many have suggested.
I'm not exactly sure what your problem actually is (is it that you can't get rid of the warning or that the <Picker> is either always rendering or never is), but I'll try to address all the problems I see.
Firstly, you shouldn't need to conditionally render the <Picker> depending on whether MyTextArea is mounted or not. Since components only render after mounting, the <Picker> will never render if the component it's in hasn't mounted.
That being said, if you still want to keep track of when the component is mounted, I'd suggest not using hooks, and using componentDidMount and componentWillUnmount with setState() instead. Not only will this make it easier to understand your component's lifecycle, but there are also some problems with the way you're using hooks.
Right now, your useRef(true) will set isMountedRef.current to true when the component is initialized, so it will be true even before its mounted. useRef() is not the same as componentDidMount().
Using 'useEffect()' to switch isMountedRef.current to true when the component is mounted won't work either. While it will fire when the component is mounted, useEffect() is for side effects, not state updates, so it doesn't trigger a re-render, which is why the component never renders when you set useRef(null).
Also, your useEffect() hook will fire every time your component updates, not just when it mounts, and your clean up function (the function being returned) will also fire on every update, not just when it unmounts. So on every update, isMountedRef.current will switch from true to false and back to true. However, none of this matters because the component won't re-render anyways (as explained above).
If you really do need to use useEffect() then you should combine it into one function and use it to update state so that it triggers a re-render:
const [isMounted, setIsMounted] = useState(false); // Create state variables
useEffect(() => {
setIsMounted(true); // The effect and clean up are in one function
return () => {
console.log('will unmount');
setIsMounted(false);
}
}, [] // This prevents firing on every update, w/o it you'll get an infinite loop
);
Lastly, from the code you shared, your component couldn't be causing the warning because there are no state updates anywhere in your code. You should check the picker's repo for issues.
Edit: Seems the warning is caused by your Picker package and there's already an issue for it https://github.com/ealush/emoji-picker-react/issues/142

React: What Controls Value of this.props.loading?

I have several React containers and components running perfectly, but I'm still studying and learning React. When my function componentDidMount runs in a new component I'm working on, it appears that all the data my component needs is available to the component, but this.props.loading is set to true;
componentDidMount() {
const a = 100; //breakpoint here
if (this.props.loading === false){
const {myDataID} = this.props;
this.fromID = Meteor.userId();
this.toID = myDataID;
this.subscribe(fromID, toID);
}
}
What controls the value in this.props.loading? Will componentDidMount run again if this.props.loading is set to false?
The prop, this.props.loading is set by the parent component. If you feel your component has all the necessary data to perform a render you don't need to check if this.props.loading is true or false. It's a good practice to have this check because some times due to network errors you might not get the data you needed. This will break your code.
componentDidMount will only be called once after the render function is done
Invoked once, both on the client and server, immediately before the initial rendering occurs. If you call setState within this method, render() will see the updated state and will be executed only once despite the state change.
But, when you change the loading prop in the parent, componentWillReceiveProps will be called with the nexProp, which is your change.
For more info, check here
What controls the value in this.props.loading?
The component that is rendering that component. I.e. if this componentDidMount method is in a component Bar, and in Foo's render method:
render() {
return <Bar loading={computeLoadingState()} />
}
Every time Foo is rerendered, Bar is potentially passed new value for loading.
Will componentDidMount run again if this.props.loading is set to false?
No. The documentation says:
Invoked once, only on the client (not on the server), immediately after the initial rendering occurs.
Other methods however are invoked whenever props change.

Resources