I need some advice on how to write tests for React component. I have 2 approaches in my mind, feel free to suggest more.
I have a component App which renders a ComplexSearchBox. I pass this ComplexSearchBox a prop onSearch, the value of this prop is a function. This function is invoked by ComplexSearchBox at various user interactions like when the user hits enter on the input box or clicks the search button.
const App = () => {
return <ComplexSearchBox onSearch={query => console.log(query)}/>;
};
I want to write tests for App component. I'm using Enzyme to write tests.
I'm following some principles of React Testing Library.
I'm mounting the component. So this will render all children as well. I don't want to render ComplexSearchBox so I'll mock it.
This is where my problem is. I have 2 approaches in my mind.
I can get the props of ComplexSearchBox and invoke the method directly with the required parameters.
jest.mock('Path To ComplexSearchBox', function ComplexSearchBox() {
return null;
});
describe('App', () => {
describe('when search button is clicked', () => {
it('should log to console by invoking prop method', () => {
const wrapper = mount(<App/>);
wrapper.find('ComplexSearchBox').props().onSearch('My random test query');
//expect something
});
});
});
I can mock the ComplexSearchBox and return a simplified version of it. Now I can type the query in an input box and then click a button to submit.
jest.mock('Path To ComplexSearchBox', function ComplexSearchBox({onSearch}) {
const [query, setQuery] = useState('Sample Search Query');
return <div>
<input value={query} onChange={setQuery}/>
<button onClick={() => onSearch(query)}/>
</div>;
});
describe('App', () => {
describe('when search button is clicked', () => {
it('should log to console by clicking', () => {
const wrapper = mount(<App/>);
wrapper.find('input').simulate('change', {target: {value: 'My random test query'}});
wrapper.find('button').simulate('click');
//expect something
});
});
});
I see value in the second approach. However, I'm not sure if it is worth the effort of creating a simplified version every time I have to interact with a child component.
The benefit of the second approach is that
It decouples the code from tests. My tests don't have to know which method to invoke with what parameter when the user wants to execute a search. Mock knows which method to invoke but that is at one place and not spread across all the tests.
I find tests to be more readable and behaviour oriented when written this way.
This mock can be extracted out and used at multiple places. Making writing tests easier.
Any method sequencing can be abstracted in the mock component. Like if I modify the ComplexSearchBox as below.
const App = () => {
return <ComplexSearchBox preProcess={()=>{}} onSearch={query => console.log(query)} postProcess={()=>{}}/>;
};
jest.mock('Path To ComplexSearchBox', function ComplexSearchBox({preProcess, onSearch, postProcess}) {
const [query, setQuery] = useState('Sample Search Query');
const search = (query) => {
const preProcessedQuery = preProcess(query);
const searchedQuery = onSearch(preProcessedQuery);
postProcess(searchedQuery);
};
return <div>
<input value={query} onChange={setQuery}/>
<button onClick={() => search(query)}/>
</div>;
});
Though I'm not very sure if the last benefit is really a benefit. As now my mock is aware of the lifecycle of ComplexSearchBox. But on the other hand, this mock will be written only once and will save me from calling those 3 methods one after the other in a lot of tests.
I could also argue that a component test written with approach one should not really care about the method sequencing as that is ComplexSearchBox responsibility.
Those 3 methods do have a tight coupling as one's output is next one's input. And now I'm borderline integration testing these 2 components.
I could also have 3 buttons which have onClick to run those 3 methods and now I can test them individually.
I'm not really sure which approach is better. I'm leaning a bit towards approach 2 because it makes my tests less dependent on implementation.
I'd appreciate any advice on this and if you have another way to test this scenario then please share.
I don't have an exact answer for you, but I highly suggest reading this article:
https://kentcdodds.com/blog/why-i-never-use-shallow-rendering
In it, you will find arguments against shallow rendering, and in favour or integration tests in React.
The points you made are all valid, if you choose the route of unit testing, therefore mocking all subcomponents of the <App /> component, you will be coupling your tests to the current implementation of those subcomponents, and anytime you change those subcomponents, even if the refactor doesn't change the behaviour, you run the risk of breaking the <App />s test.
I don't believe there is a way around it when you are dealing with mocks, so I would suggest considering the article above and not mocking anything, which might run the tests a little slower, but it probably won't be a big deal most of the time!
Related
I read this article React Hook to Run Code After Render and came across this line:
React can and will sometimes call your components multiple times before actually rendering them to the screen, so you can’t rely on “one call == one render”.
What did he mean?
I wrote this code
function x() {
console.log("x");
}
function y() {
console.log("y");
return "y";
}
function Silicon() {
console.log("silicon");
return <div></div>;
}
function useDarko() {
const [count1, setCount1] = useState(0);
console.log("useDarko");
return [count1, setCount1];
}
export default function Test0022() {
const [darko, setDarko] = useDarko();
const [count1, setCount1] = useState(0);
x();
useEffect(() => {
if (count1 !== 200)
setTimeout(() => {
setCount1((e) => ++e);
}, 100);
}, [count1]);
return (
<>
<Silicon />
{y()}
</>
);
}
I always see "804 console.log". It works as expected, one call == one render.
For about four years, React has been working to implement a feature called "concurrent mode" (yes, it had been announced long before that article was released). It will be in version 18 of React, which is currently in a release-candidate state.
Concurrent mode allows React to abort a long-running render part way through in order to handle a more important update. This new approach has implications for lifecycle hooks and for the behavior of side effects, and as a result the React team has been training the community to start writing their code in a way that works with concurrent mode. For example, they deprecated several class component lifecycle hooks that would not be safe with the new approach.
And as the article mentions, one of the mental models we need to get used to is that a component may be called multiple times before it actually makes it on to the screen. Your component may get called, run all the way through, and then React realizes it needs to throw out that work and start over. These cases are rare, but to help you catch problems you can use strict mode. Among other things, it will deliberately double-render your components in development builds, to make it easier to spot bugs that only occur when these double-rendering cases occur.
Note that in React 17 this strict-mode double-render overwrites the console.log function so that it has no effect, and as a result it's difficult to see that it's happening.
I have a very large and complex React application. It is designed to behave like a desktop application. The interface is a document style interface with tabs, each tab can be one of many different type of editor component (there are currently 14 different editor screens). It is possible to have a very large number of tabs open at once (20-30 tabs). The application was originally written all with React class components, but with newer components (and where significant refactors have been required) I've moved to functional components using hooks. I prefer the concise syntax of functions and that seems to be the recommended direction to take in general, but I've encountered a pattern from the classes that I don't know how to replicate with functions.
Basically, each screen (tab) on the app is an editor of some sort (think Microsoft office, but where you can have a spreadsheet, text document, vector image, Visio diagram, etc all in tabs within the same application... Because each screen is so distinct they manage their own internal state. I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex. Each screen needs to be able to save it's current working document to the database, and typically provides a save option. Following standard object oriented design the 'save' function is implemented as a method on the top level component for each editor. However I need to perform a 'save-all' function where I iterate through all of the open tabs and call the save method (using a reference) on each of the tabs. Something like:
openTabs.forEach((tabRef) => tabRef.current.save());
So, If I make this a functional component then I have my save method as a function assigned to a constant inside the function:
const save = () => {...}
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level. Aside from the fact that would make it very difficult to find and maintain, it also would break my modular loading which only loads the component when needed as the save would have to be at a level above the code-splitting.
The only solution to this problem that I can think of is to have a save prop on the component and a useEffect() to call the save when that save prop is changed - then I'd just need to write a dummy value of anything to that save prop to trigger a save... This seems like a very counter-intuitive and overly complex way to do it.... Or do I simply continue to stick with classes for these components?
Thankyou,
Troy
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level.
You should ask yourself if the component should be smart vs dumb (https://www.digitalocean.com/community/tutorials/react-smart-dumb-components).
Consider the following:
const Page1 = ({ onSave }) => (...);
const Page2 = ({ onSave }) => (...);
const App = () => {
const handleSavePage1 = (...) => { ... };
const handleSavePage2 = (...) => { ... };
const handleSaveAll = (...) => {
handleSavePage1();
handleSavePage2();
};
return (
<Page1 onSave={handleSavePage1} />
<Page2 onSave={handleSavePage2} />
<Button onClick={handleSaveAll}>Save all</button>
);
};
You've then separated the layout from the functionality, and can compose the application as needed.
I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex.
I don't know if for some reason Redux is totally out of the picture or not, but I think it's one of the best options in a project like this.
Where you have a separated reducer for each module, managing the module's state, also each reducer having a "saveTabX" action, all of them available to be dispatched in the Root component.
https://reactjs.org/docs/hooks-faq.html#how-to-test-components-that-use-hooks
the document show that we should use React DOM to test component.But in many cases, our logical components render another ui component.And maybe the function in logical component will be passed to the ui component as a prop, just like
function Foo(){
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
function onChange(value) {
setCount(value);
}
return <Bar value={count} onChange={onChange} />
}
function Bar(props){
const { value, onChange } = props;
return (
<div>
<p>You clicked {value} times</p>
<button onClick={() => onChange(count + 1)}>
Click me
</button>
</div>
);
}
In this case, how can I test the onChange() and other functions which can't be obtained in DOM?This is the simplest case. If we use some component library like material-design and ant-design, we usually don't know the DOM structure.
As the docs suggest:
If your testing solution doesn’t rely on React internals, testing
components with Hooks shouldn’t be different from how you normally
test components.
The goal is not to test the onChange function at all, whether the hooks work or not is already tested by the React team.
Your current change to render a component does not change the test at all, rendering Foo still renders a button and p however deep the component chain goes.
On using a framework like Antd or Material Design, it might be difficult to know the complete DOM structure. But instead, or better you can query by things that the user sees on your page.
For eg. using the React testing library recommended in the docs:
const button = getByText(container, /Click Me/i);
This ties in directly with what user is seeing on the page and leads to much better tests.
I have useEffect, which gets two values from server 1, price of item 2, name of item. when I use the setPriceSquanchy functions (obj.price) setNameSquanchy (obj.name). my code is updated twice there is no way to make it so that it is updated only once.
import React, { useState,useEffect} from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [priceSquanchy, setPriceSquanchy] = useState("");
const [nameSquanchy, setNameSquanchy] = useState("");
useEffect(() => {
(async () => {
const res = await fetch(
`https://foo0022.firebaseio.com/vz.json`
);
const obj = await res.json();
setPriceSquanchy(obj.price)
setNameSquanchy(obj.name)
})();
}, []);
console.log("Hello")
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
TL;DR Your sample. Here is the fixed version (another fix with ReactDOM.unstable_batchUpdates here)
So your component is rendered twice after the promise is resolved right? Like this logs "App invoked" twice after logging "setting states". This is because first setA is called, then setB is called both causing a render.
In my opinion this is fine because anyway React will only apply the necessary patches to the DOM. It won't be a huge performance difference even if you fix it.
But if you want to fix it you can have a state containing both price and name something like { price: "", name: "" } in that way you'll only call setPriceName({ price: newPrice, name: newName }). Demo. As you see in this demo "App invoked" is only logged once after "setting states" is logged.
If you don't want to do that you can also use ReactDOM.unstable_batchedUpdates like this. As you can see this also works but it's "unstable". More on the API by Dan here and in this thread too
Also, at times React could also batch updates from setA and setB thus causing only one render. Like here. Here it got batched together because it didn't had a timeout and it was immediately after first render.
Focusing more on the question "Where is it better to update the values of the hooks if the value received from the server?"...
What you are doing is pretty correct. Another way would be making a container component for fetching the data then having a presentational component to actually render it. I don't really like this approach (nor does Dan suggests it now xD) it's really an overkill. You can read more on that here.
PS: Also in case you are wondering why there are still two renders in your sample's fixed version, well the first one is the initial render. So there's basically only one update after the promise is resolved.
useEffect is used for side effects.
From React Docs
Accepts a function that contains imperative, possibly effectful code.
Mutations, subscriptions, timers, logging, and other side effects are not allowed inside the main body of a function component (referred to as React’s render phase). Doing so will lead to confusing bugs and inconsistencies in the UI.
Instead, use useEffect. The function passed to useEffect will run after the render is committed to the screen.
Now, what you're doing is in fact a side effect ( AJAX call) and here's a working demo to achieve the same via useEffect.
Notice the use of second argument to useEffect. This is dependency array and useEffect will only fire accordingly. By supplying an empty array, I make sure it fires runs only once. Failing to do this will continuously call this function in our component. You can also control it's firing based on some other state variables. More on reading here.
useEffects is a good place to do this. useEffects hook is intended for side effects, e.g. fetch request.
If a request is supposed to be done once on component mount and not on every render, useEffects should be used with empty inputs.
The function mixes async..await and promises in unnecessarily complex way.
It should be:
useEffect(() => {
(async () => {
const res = await fetch(
`......`
);
const obj = await res.json();
setPriceSquanchy(obj.price)
setNameSquanchy(obj.name)
})();
// return fetch cancellation function
}, []);
I'm testing Hero component using jest & enzyme. I thought to my mind that I can do the same thing in the two ways.
For example, I want to get a component's state.
test('Test description', () => {
const element = shallow(
<Hero />
);
// 1
expect(element.state()).toBeTruthy();
// 2
expect(element.instance().state).toBeTruthy();
});
I have two variants to write this code: using .instance().state or .state(). Probably there are recommendations how to write code like that?
state() is a shortcut for instance().state that provides meaningful error message in case it's called on wrong wrapper.
Since state() exists in Enzyme API, it's intended to be used for this purpose. It takes less characters to type than instance().state.