Typeerror: Cannot Read Property 'simulate' of Undefined
JavaScript Frameworks play an of import role in creating modernistic spider web applications. They provide developers with a diverseness of proven and well-tested solutions for creating efficient and scalable applications. Nowadays, it's hard to detect a company that builds its frontend products without using any framework, so knowing at to the lowest degree ane of them is a necessary skill for every frontend developer. To truly know a framework, nosotros need to understand non just how to create applications using it, but also how to examination them.
JavaScript frameworks
At that place are lots of JS frameworks for creating spider web applications, and every year in that location are more and more than candidates to take the leading position. Currently, three frameworks play the main function: Angular, React and Vue. And in this article, I would like to focus on the most popular one - React.
To examination or not to exam
When learning how to create applications using a given framework, developers often forget, or intentionally ignore, the necessity of testing already developed applications. Equally a issue, you can oft find complicated React applications with hundreds of components and zippo or very few tests, which never ends well.
In my stance, testing is and so important considering:
- for well-tested applications, the chances are great, that the developer will fix any bugs before pushing to the repository,
- having many well-written tests makes it easier for the developer to change the code, because he immediately knows if the awarding is still working properly,
- we can say that tests are also documentation - and what is even better, such a documentation is always up to date, because when code is changed and test fails we take to update this test.
While testing React components may seem complicated at outset, the sooner nosotros start learning the easier information technology will be to practice it well over fourth dimension and with little attempt. In this post I would like to nowadays one of the possibilities of testing React components.
React testing tools
The set of frameworks and tools for testing React applications is very big, and so at the beginning of the testing run a risk the question is: what to cull? Instead of listing all the nearly pop tools, I would like to present those that I apply every day, Jest + Enzyme.
Jest - One of the most popular (7M downloads each calendar week) and very efficient JS testing framework, recommended by React creators.
Enzyme - React component testing tools. By adding an abstraction layer to the rendered component, Enzyme allows you lot to dispense the component and search for other components and HTML elements within it. Another advantage is also well-written documentation that makes it easier to kickoff working with this tool.
This combination of the exam framework (Jest), and the component manipulation tool (Enzyme) enables the creation of efficient unit and integration tests for React components.
React components and how to exam them
Enough theory, permit's lawmaking!
I will start with a very basic component instance together with tests. Later this component will be enriched with more advanced mechanisms (Router, Redux, Typescript) and I will present how to adjust tests, so that they still work and pass.
Bones component
This is a simple React component instance.
export const UserInfoBasic = ({ user }) => { const [ showDetails , setShowDetails ] = useState ( false ); const renderUserDetails = () => ( < div className = { styles . details } > < Typography variant = "h5" >Details</ Typography > < p >Login: { user . login } </ p > < p >Electronic mail: { user . e-mail } </ p > < p >Age: { user . age } </ p > </ div > ) const getUserFullName = () => ` ${ user . name } ${ user . lastName } ` ; const toggleDetails = () => setShowDetails ( ! showDetails ); return ( < Paper square = { truthful } className = { styles . paper } > < Typography variant = "h4" >Info about user: { getUserFullName () } </ Typography > < p >Beginning name: { user . name } </ p > < p >Last name: { user . lastName } </ p > < Button onClick = { toggleDetails } > { showDetails ? ' Hibernate ' : ' Show ' } user details</ Push button > { showDetails && renderUserDetails () } </ Newspaper > ); };
The role of UserInfoBasic is to present user data. Component receives a user object in props and displays user info in ii sections. The first section contains the get-go and last proper noun and is visible all the fourth dimension. The second department provides details and is subconscious later the first render, only can be viewed by clicking a button.
You tin see what is rendered by this component initially and after push click on these screenshots.
This component does not utilize any advanced React mechanisms or external libraries. Tests of such a component written with Jest + Enzyme are very uncomplicated and intuitive.
const user = { name : ' Darek ' , lastName : ' Wojtowicz ' , historic period : 28 , login : ' dariuszwojtowicz ' , electronic mail : ' dar.wojtowicz@postal service.com ' }; describe ( ' UserInfoBasic ' , () => { draw ( ' Initial state ' , () => { const wrapper = mountain ( < UserInfoBasic user = { user } /> ); test ( ' should render header with full name ' , () => { expect ( wrapper . observe ( Typography ). text ()). toContain ( ' Info most user: Darek Wojtowicz ' ); }); examination ( ' should not render details ' , () => { await ( wrapper . findWhere (( northward ) => n . text () === ' Login: dariuszwojtowicz ' ). length ). toEqual ( 0 ); }); test ( ' should render "show user details" button ' , () => { expect ( wrapper . find ( Button ). text ()). toEqual ( ' Prove user details ' ); }); }); describe ( ' After "Evidence user details" push click ' , () => { const wrapper = mount ( < UserInfoBasic user = { user } /> ); wrapper . notice ( Push ). simulate ( ' click ' ); examination ( ' should render details ' , () => { look ( wrapper . findWhere (( due north ) => n . text () === ' Login: dariuszwojtowicz ' ). length ). toEqual ( ane ); }); test ( ' should return "Hide user details" button ' , () => { expect ( wrapper . find ( Button ). text ()). toEqual ( ' Hide user details ' ); }); }); });
Get-go we define the user object, which we pass in the props of the tested component in all tests. Then we use the depict methods of the Jest framework to separate the tests into logical sets. In this instance, two sets have been defined.
The first i, Initial land, is used to exam the component in the initial state, without user interaction. The 2d one is for testing the component later on clicking the Show user details button. Other Jest framework methods used here are the test method, which is used to write a single test, and the expect method, which checks if the condition nosotros prepare is met.
In society to test a component, we need to accept an instance of it, we need to render information technology somehow. This is where Enzyme comes in handy, which we can hands use to simulate creating and rendering a component. We can use 1 of the 3 methods (shallow, mount or render) to achieve this. In this instance I use the mount function. The UserBasicInfo component was mounted using the mount method, with the user object that I divers earlier. This method returns an object, which tin be used to check what was rendered and for interaction with the rendered component. This object is stored in a variable named wrapper.
Then on such an object we use the find method to check the content of the rendered component. In the first examination, nosotros check if the component has correctly rendered the user'due south first and final name in the appropriate element. In the 2nd test, nosotros make sure that user details are not visible at beginning. And in the third, whether a button has been rendered to brandish user details.
In the second set of tests, we mount the component again and immediately afterwards, we employ simulate method to simulate a user pressing the button.
As a outcome, the state of our component is now exactly as if the user had entered the page and clicked the push once. And then, in the first exam, we check whether the user details are displayed. In the second test, we check whether the text on the button has inverse from 'Show user details' to 'Hide user details'.
More tests can be added, simply for the purpose of showing the use of Jest + Enzyme for a simple component it is more than enough.
Component with Redux
Circuitous projects written in React are very mutual. When an awarding consists not of several, but of dozens or even hundreds components, there is nigh always a problem with managing the state. I solution is to use Redux, a predictable state container for JavaScript applications. In other words, Redux is an application data-flow architecture, because information technology maintains the state of an application in a single immutable tree object. This object can't exist inverse directly, only using actions and reducers which create a new object.
Below you can see the implementation of the previous component adjusted to piece of work with Redux.
const mapStateToProps = land => ({ currentUser : country . currentUser }); const mapDispatchToProps = acceleration => ({ updateEmail : email => acceleration ( updateEmail ( email )) }); const UserInfoReduxComponent = ({ currentUser , updateEmail }) => { const [ showDetails , setShowDetails ] = useState ( fake ); const renderUserDetails = () => ( < div className = { styles . details } > < Typography variant = "h5" >Details</ Typography > < p >Login: { currentUser . login } </ p > < p >Age: { currentUser . historic period } </ p > < TextField blazon = "text" label = "Email" value = { currentUser . email } onChange = { changeEmail } /> </ div > ) const getUserFullName = () => ` ${ currentUser . name } ${ currentUser . lastName } ` ; const toggleDetails = () => setShowDetails ( ! showDetails ); const changeEmail = ( effect ) => updateEmail ( event . target . value ); return // Return statement hasn't inverse }; export const UserInfoRedux = connect ( mapStateToProps , mapDispatchToProps )( UserInfoReduxComponent );
Only a few things accept inverse in comparision to the basic version of this component. New function mapStateToProps is responsible for mapping the Redux land to props of our component. The second function mapDispatchToProps is responsible for assigning Redux actions to component properties. With these actions the component is able to modify the state managed by Redux (in this example the component can change the user's e-mail using the updateEmail action).
Instead of read-only text containing the user's email address, an editable Textfield has appeared. At present, when email is changed, the updateEmail activeness is dispatched and email is inverse in Redux shop.
The terminal change in the component implementation is the use of the connect function from Redux, thanks to which our component is connected to global application state.
However, for the component to be able to piece of work with Redux, nosotros need to provide a Redux store in our awarding. And this is done as follows:
< Provider store = { store } > < App /> </ Provider >
At present our component works with Redux, but our tests are unaware of this change. Running them ends up with the following error:
Error : Could not notice " store " in the context of " Connect(UserInfoReduxComponent) " .
This error means that nosotros are trying to return a component with Enzyme that is at present closely related to Redux, without providing whatever Redux context. The component has no admission to the store.
Fortunately, the solution to this problem is very simple. The tests should be extended so that the component has access to the Redux Provider.
const user = {...}; const mockStore = configureStore ([]); const store = mockStore ({ currentUser : user }); const dispatchMock = () => Promise . resolve ({}); store . dispatch = jest . fn ( dispatchMock ); depict ( ' UserInfoRedux ' , () => { describe ( ' After "Show user details" button click ' , () => { const wrapper = mount ( < Provider store = { store } > < UserInfoRedux /> </ Provider >, { context : { store } } ); wrapper . find ( Push button ). simulate ( ' click ' ); exam ( ' should update user email in store later input value change ' , () => { // when wrapper . find ( ' input ' ). simulate ( ' change ' , { target : { value : ' new@email.com ' }}); // and so expect ( shop . dispatch ). toHaveBeenCalledWith ( { email : " new@email.com " , type : " UPDATE_EMAIL " }); }); }); });
We take the new mockStore function created with configureStore from the redux-mock-store package, which is used to create a mocked Redux Store containing user information in the currentUser field. And so nosotros create a mock for the dispatch object, which is responsible for performing actions that change the Redux state.
The last step is to render the UserInfoRedux component within the Redux Provider and pass our mocked store to the mount function.
Thank you to these changes, the tests pass over again.
At that place is also one new test that simulates changing an email address and checks if the changes were performed on the Redux state. This way, we can exam whether user interactions are reflected in the application state stored in Redux.
Component with Router
Another solution often establish in larger projects that really simplifies edifice applications is routing. React Router is responsible for routing in React applications. Thank you to it, we can, for example, apply the same component at unlike addresses, in a slightly different mode. As an case, I used the same component that displays user data. At the /profile address it displays the data of a currently logged user and allows to alter the electronic mail address. On the other mitt, at the /users/{id} address the same component displays the user with given identifier, and it is read-only. Hither are the changes in the component implementation that I fabricated to achieve this result:
const mapStateToProps = state => ({ currentUser : state . currentUser , users : state . users }); const mapDispatchToProps = dispatch => ({ updateEmail : email => dispatch ( updateEmail ( email )) }); const UserInfoReduxRouterComponent = ({ currentUser , users , updateEmail , location , lucifer }) => { const [ showDetails , setShowDetails ] = useState ( false ); const renderUserDetails = () => ( < div className = { styles . details } > < Typography variant = "h5" >Details</ Typography > < p >Login: { userData . login } </ p > < p >Age: { userData . age } </ p > < TextField blazon = "text" label = "Email" value = { userData . email } onChange = { changeEmail } disabled = { location . pathname !== ' /contour ' } /> </ div > ); const getUserFullName = () => ` ${ userData . name } ${ userData . lastName } ` ; const toggleDetails = () => setShowDetails ( ! showDetails ); const changeEmail = ( issue ) => { if ( location . pathname === ' /profile ' ) { updateEmail ( event . target . value ); } }; const getUserData = () => { if ( location . pathname === ' /profile ' ) { return currentUser ; } else { const foundUser = users . discover (( user ) => user . id == lucifer . params . id ); if ( foundUser ) { render foundUser ; } } return zippo ; }; const userData = getUserData (); const renderUserInfo = () => ( <> < Typography variant = "h4" >Info nigh user: { getUserFullName () } (id: { userData . id })</ Typography > < p >Offset proper noun: { userData . name } </ p > < p >Terminal proper noun: { userData . lastName } </ p > < Push button style = { { ' edge ' : ' 1px solid grey ' } } onClick = { toggleDetails } > { showDetails ? ' Hide ' : ' Evidence ' } user details</ Push button > { showDetails && renderUserDetails () } </> ); return ( < Paper square = { true } className = { styles . paper } > { userData && renderUserInfo () } { ! userData && < h3 >User with given id does not be</ h3 > } </ Newspaper > ); }; export const UserInfoReduxRouter = connect ( mapStateToProps , mapDispatchToProps )( UserInfoReduxRouterComponent );
Then what has changed? At that place is the new users belongings that comes from Redux store. This prop is the list of all existing users. There is the new disabled property on the email field. With this property, email is editable only at the /profile address. The most important affair is how we define the userData variable considering this variable is the source of information for rendering user data. First, nosotros define userData as cypher. If the current pathname is /contour then we assign a currently logged user to userData. If the address is different, information technology means that we are on the /users/{id} folio. In this case, we search for a user with a given identifier in a listing of all users. And then there are three possible results:
- current user information is rendered at the /profile page,
- a user with a given id is rendered at the /users/{id} page,
- text "User with given id does not exist" is rendered if in that location is no user with a given id on the listing.
We demand one more change if we desire our component to work under both addresses. Nosotros have to return this component within BrowserRouter component, that comes from React Router, like this:
< BrowserRouter > < Switch > < Route exact path = "/contour" component = { UserInfoReduxRouter } /> < Route exact path = "/users/:id" component = { UserInfoReduxRouter } /> </ Switch > </ BrowserRouter >
The higher up lawmaking defines the routing of the application using the BrowserRouter component. Nosotros tell the router which component should be loaded for a given address. That's all.
Unfortunately, the tests stopped working again, and here's the mistake we become after running them:
Fault : Uncaught [ TypeError : Cannot read holding ' pathname ' of undefined ]
This mistake ways that during the examination execution, the component does not accept access to the location object from which the pathname attribute is retrieved. As mentioned higher up, this object is provided by React Router at runtime. In tests, however, nosotros take to provide it differently. Here's the snippet of the test code that is responsible for information technology:
const path = `/profile` ; const match = { isExact : true , path , url : path }; const location = createLocation ( friction match . url ); const shop = getStore (); const wrapper = mountain ( < Provider store = { shop } > < UserInfoReduxRouter match = { match } location = { location } /> </ Provider >, { context : { store } } );
First, we define the accost at which we want to test our component. Next, we create a mock for the lucifer object and pass the accost to it (the path variable). Using the createLocation part that comes from the history package, we create a mock for the location object. Finally, we pass the created mocks of friction match and location objects to the component props. Thanks to these changes, nosotros provided the routing context and the component can work properly. Tests pass over again.
Component with TypeScript
The last tool I would like to mention is the TypeScript language. Imagine a huge React projection with 20 developers and hundreds of components. Each component accepts several props. Such a project written in JS, without typing, would be very difficult to maintain. Programmers have to guess variable types. Is it, a string? or maybe a number? What fields must be divers in the user belongings for the component to piece of work properly? That is why TypeScript was created, it is a language that is a superset of JavaScript, and it introduces static type checking. Looking at the implementation of the component above, we can see what props the component takes. This is, for instance, currentUser, only we do not know what backdrop should such object provide. Is electronic mail required? Can we skip age? TypeScript solves this kind of issues.
Typescript too makes writing and maintaining tests easier, considering:
- nosotros don't have to look at the component implementation every few seconds merely to verify its API thank you to static types,
- when some public interface is changed and it was previously used in some test, nosotros don't demand to run the tests to know which of them fails - they only won't compile,
- we exercise non need to write test cases where we are passing wrong types to tested function or component - Typescript makes sure that at that place are no such situations in code.
Now let's see how our component looks similar, written in TypeScript.
First, we define the interface that describes a user:
export interface User { id : number ; proper noun : string ; lastName : string ; login : cord ; email : string ; age ?: number ; }
The interface clearly defines which fields describe a user object and which fields are not required. In this case, the age field is optional.
Here is the use of TypeScript in the component itself:
const mapStateToProps = ( state : { currentUser : User ; users : User []; }) => ({ currentUser : country . currentUser , users : country . users }); const mapDispatchToProps = ( dispatch : Dispatch ) => ({ updateEmail : ( email : string ) => acceleration ( updateEmail ( email )) }); consign interface UserInfoReduxRouterTsProps { currentUser : User ; users : User []; updateEmail : ( email : cord ) => UpdateEmailAction ; location : Location ; } const UserInfoReduxRouterTsComponent : React . FC < UserInfoReduxRouterTsProps > = ( { currentUser , users , updateEmail , location } ) => { const [ showDetails , setShowDetails ] = useState ( faux ); const renderUserDetails = (): JSX . Chemical element => ( < div className = { styles . details } > < Typography variant = "h5" >Details</ Typography > < p >Login: { userData . login } </ p > < p >Age: { userData . age } </ p > < TextField fullWidth type = "text" characterization = "Email" value = { userData . email } onChange = { changeEmail } disabled = { location . pathname !== ' /profile ' } /> </ div > ); const getUserFullName = (): JSX . Chemical element => ` ${ userData . name } ${ userData . lastName } ` ; const toggleDetails = (): void => setShowDetails ( ! showDetails ); const changeEmail = ( event : React . ChangeEvent < any > ): void => { if ( location . pathname === ' /profile ' ) { updateEmail ( event . target . value ); } } const getUserId = (): number => parseInt ( location . pathname . dissever ( ' users/ ' )[ 1 ], ten ); const getUserData = (): User => { if ( location . pathname === ' /contour ' ) { return currentUser ; } else { const foundUser = users . find (( user : User ) => user . id == getUserId ()); if ( foundUser ) { return foundUser ; } } render zero ; }; const userData : User = getUserData (); // No more than changes, remainder is the same. }; export const UserInfoReduxRouterTs = withRouter ( connect ( mapStateToProps , mapDispatchToProps )( UserInfoReduxRouterTsComponent ));
The component has inverse a lot. The nigh important thing is the definition of the UserInfoReduxRouterTsProps interface, which describes what props the component takes, which are optional and what types they accept. As a effect, the programmer who wants to use such a component knows immediately what to pass to it. Other changes are the appearance of types for parameters in functions besides equally functions return types. Both are very helpful for futurity changes in component or code refactoring.
After these changes tests stopped working once again. The error is:
Type ' Location<{}> ' is missing the following properties from type ' Location ' : ancestorOrigins , host , hostname , hef , and half-dozen more .
So far in tests, we passed the mocked location object direct as component props. Now TypeScript detects that the blazon of passed location object is not identical to the type expected by the component. To fix this result we tin can simply simulate a context similar to the one in which the component lives while the application is running. To practise this, nosotros take to embed the component in the context of the router. With this modify the location parameter comes from Router, and it has exactly the type that the components expect.
const path = `/contour` ; const shop = getStore (); const wrapper = mount ( < MemoryRouter initialEntries = { [ path ] } > < Provider store = { store } > < UserInfoReduxRouterTs /> </ Provider > </ MemoryRouter >, { context : { store } } );
The MemoryRouter component is imported from the 'react-router' package. We define a variable path to simulate the behavior of the component at a specific address (here /contour). Then, we mount our component wrapped in MemoryRouter, and pass the previously divers path as prop.
Cheers to these changes, the tests pass again.
Conclusion
Every bit I mentioned at the commencement, it'south hard to find frontend applications that are created without the use of frameworks these days. As can be seen in this text, it is also difficult to create circuitous projects without introducing additional libraries similar Router, Redux or TypeScript. You lot can also find that including them to the projection requires some changes to the tests too. However, if nosotros take tests seriously from the very commencement of application development, and we create them regularly, we can rapidly and efficiently adapt them to changes in the awarding.
Only would information technology be easy if we wrote tests after the application is already developed and uses all these extensions? Probably non, often if we do not start testing the awarding from the starting time, then the cost of introducing the tests is so high, that nosotros decide to give them up, and our application loses a lot of value, because without tests information technology is difficult to maintain the app and introduce further changes.
So, I encourage y'all to exam!
If you are interested in the implementation details take a wait at the testing-react-components github repository.
Source: https://blog.allegro.tech/2020/11/testing-react-applications.html
0 Response to "Typeerror: Cannot Read Property 'simulate' of Undefined"
Post a Comment