my component has multiple selectors:
import { useSelector } from 'react-redux'
...
const data1 = useSelector(xxxxx)
const data2 = useSelector(yyyyy)
How properly mock each in test file?
import { useSelector } from 'react-redux'
jest.mock('react-redux', () => ({
useSelector: jest.fn()
}))
....
useSelector.mockImplementation(() => ({
dataready: true
}))
which selector it's really mocking in this case?
Don't mock the selector. You want to test the integration between Redux and React components, not the Redux implementation of selectors. If you use react-testing-library it's pretty simple to hijack the render() method and implement your store using a Redux Provider component. Here are the docs for testing Connected Components.
Here's your test re-written with the user in mind:
import { render } from '../../test-utils' // <-- Hijacked render
it('displays data when ready', { // <-- behavior explanation
const {getByTestId} = render(<YourComponent />, {
initialState: {
dataready: true // <-- Pass data for selector
}
})
expect(getByTestId('some-testId')).toBeTruthy(); // <-- Check that something shows based on selector
})
import * as redux from 'react-redux';
...
beforeEach(() => {
jest
.spyOn(redux, 'useSelector')
.mockReturnValueOnce(xxxx)
.mockReturnValueOnce(yyyy);
});
You'd want to do something like this, to get your spy, and then check on what it is called with and mockImplementation to prevent async if thats an issue for you, i'd suggest you provide state via the render function, rather than mock a selector implementation though.
import { useDispatch, useSelector } from 'react-redux';
const reactRedux = { useDispatch, useSelector };
const useDispatchMock = jest.spyOn(reactRedux, 'useDispatch');
Related
Unless I'm mistaken, I believe I've found a bug in how rerenders are triggered (or in this case, aren't) by the #testing-library/react package. I've got a codesandbox which you can download and reproduce in seconds:
https://codesandbox.io/s/asynchronous-react-redux-toolkit-bug-8sleu4?file=/README.md
As a summary for here, I've just got a redux store and I toggle a boolean value from false to true after some async activity in an on-mount useEffect in a component:
import React, { useEffect } from "react";
import { useAppDispatch } from "../hooks/useAppDispatch";
import { setMyCoolBoolean } from "../redux/slices/exampleSlice";
import AnotherComponent from "./AnotherComponent";
export default function InnerComponent() {
const dispatch = useAppDispatch();
const fetchSomeData = async () => {
await fetch("https://swapi.dev/api/people");
dispatch(setMyCoolBoolean(true));
};
// on mount, set some values
useEffect(() => {
fetchSomeData();
}, []);
return <AnotherComponent />;
}
Then, in a different component, I hook into that store value with useAppSelector hook and then useEffect to do something local there (dumb example, but it illustrates my point.):
import { useEffect, useState } from "react";
import { useAppSelector } from "../hooks/useAppSelector";
export default function AnotherComponent() {
const { myCoolBoolean } = useAppSelector((state) => state.example);
const [localBoolean, setLocalBoolean] = useState(false);
// when myCoolBoolean changes, set the local boolean state value in this component
// somewhat a dumb example but it illustrates
// the failure of react-testing-library
useEffect(() => {
// only do something in this component
// if myCoolBoolean changes to true
if (myCoolBoolean) {
console.log("SET TO TRUE!");
setLocalBoolean(myCoolBoolean);
}
}, [myCoolBoolean]);
if (localBoolean) {
return <span data-testid="NEW">I'm new</span>;
}
return <span data-testid="ORIGINAL">I'm original</span>;
}
In result, my test would like to see if the 'new' value is ever shown. Despite issuing rerender, you will see the test fails:
import React from "react";
import { render } from "#testing-library/react";
import App from "../../src/App";
import { act } from "react-test-renderer";
import 'whatwg-fetch'
test("On mount, boolean value changes, causing our new span to show up", async () => {
const { getByTestId, rerender } = render(<App />);
await act(async () => {
// expect(getByTestId("ORIGINAL")).toBeTruthy();
// No matter how many times you call rerender here,
// you'll NEVER see the "NEW" test id (and thus corresponding <span> element) appear in the document
// despite this being the case in any standard browser
await rerender(<App />);
// If you comment this line below out, the test passes fine.
// test ID "ORIGINAL" is found, but "NEW" is never found!!!!
expect(getByTestId("NEW")).toBeTruthy();
});
});
Behaviour is totally as expected in a browser, but fails in my jest test. Can anybody guide me on how to get my test to pass? As far as I know, the code and implementations of my React components and Redux are the cleanest and best practices that are currently out there, so I'm more expecting this is a gross misunderstanding on my part of how #testing-library works, though I thought rerender would do the trick.
I've apparently misunderstood how react-testing-library works under the hood. You don't even need to use rerender or act at all! Simply using a waitFor with await / async is enough to trigger the on mount logic and subsequent rendering:
import React from "react";
import { findByTestId, render, waitFor } from "#testing-library/react";
import App from "../../src/App";
import { act } from "#testing-library/react-hooks/dom";
import "whatwg-fetch";
test("On mount, boolean value changes, causing our new span to show up", async () => {
const { getByTestId, rerender, findByTestId } = render(<App />);
// Works fine, as we would expect
expect(getByTestId("ORIGINAL")).toBeTruthy();
// simply by using 'await' here, react-testing-library must rerender somehow
// note that 'act' isn't even used or needed either!
await waitFor(() => getByTestId("NEW"));
});
Another case of "overthinking it" gone bad...
I use reselect library with my react-native project. I have the following code.
import { createSelector } from 'reselect';
import { ApplicationState } from 'types/reducer.types';
const selectAuth = ({ auth }: ApplicationState) => auth;
export const selectFirstName = createSelector([selectAuth], auth => auth.user?.firstName);
export const selectLastName = createSelector([selectAuth], auth => auth?.user?.lastName);
In the container the code is like this.
import { connect } from 'react-redux';
// import { useSelector } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { signOut } from 'Redux/auth/auth.actions';
import { selectFirstName, selectLastName } from 'Redux/auth/auth.selectors';
import CustomDrawer from './custom-drawer.component';
const mapStateToProps = createStructuredSelector({
firstName: selectFirstName,
lastName: selectLastName,
});
export default connect(mapStateToProps, { signOut })(CustomDrawer);
Now I want to use useSelector hook. I tried several ways but it didn't work. How can this be done?
createStructuredSelector returns a selector (a function that takes state and returns an object {firstName, lastName}), so you can use it inside of useSelector
const {firstName, lastName} = useSelector(createStructuredSelector({
firstName: selectFirstName,
lastName: selectLastName,
}));
or using your existing variable
const {firstName, lastName} = useSelector(mapStateToProps);
However there's not really any point in combining and then destructing the properties when you can use many useSelector hooks in one component.
const firstName = useSelector(selectFirstName);
const lastName = useSelector(selectLastName);
useSelector and useDispatch are two hooks that can be imported from react-redux package and these two can easily replace the need for using connect.
Here is how you can use them:
import { useDispatch, useSelector } from 'react-redux';
import {someAction} from '../user/userActions';
const YourComponent = () => {
// useDispatch returns back a dispatch function
const dispatch = useDispatch();
// useSelector selects a portion of the state, it plays the role and
// replace the need for mapStateToProps in connect higher order function
const currentUser = useSelector(state => state.user.currentUser);
return(
<>
<h1>{ currentUser.firstName }</h1>
// Here you can use dispatch to dispatch an action
<button onClick={() => dispatch(someAction)}></button>
</>
)
};
export default YourComponent;
Overall, you can see that by using useSelector and useDispatch we removed so much boilerplate from our code(connect, mapStateToProps, mapDispatchToProps, createStructuredSelector removed) and it looks clean and more readable and still works as previous.
Here is the official resource you can read more about the two above-mentioned hooks.
React-Redux Hooks
P.S. You can also send a selector created using reselect or whatever selector of your choice into useSelector.
With react hooks coming, should we use prop-types for React custom hooks e.g,
import React from 'react';
import PropTypes from 'prop-types';
const useTitle = title => {
React.useEffect(() => {
document.title = title;
}, [title]);
}
useTitle.propTypes = {
title: PropTypes.string.isRequired,
};
export default useTitle;
Is the above a good approach to validate the param(s) passed to a custom react hooks or should there be a different way for validation the props/params passed to custom hook which is basically just a simple function.
No. React doesn't validate custom hooks or in-built hooks props.
Look here for updateFunctionComponent, It validates prop types using checkPropTypes for a react component and then render it with hooks i.e. check out renderWithHooks.
Now if you check here in renderWithHooks method, It updates the dispatcher and calls your functional component which in turn calls your custom hook, since it's just another function call inside your functional component.
Your custom hook will call in-built hooks. You can check the implementation here . Based on type of dispatcher it will call built-in hooks.
If you search checkPropTypes in the whole file you won't find the validation logic or prop-types/checkPropTypes dependency which is used to validate the prop types.
Here is some nice article about how react hooks works
I'm using PropTypes.checkPropTypes for useSelector hook. And it works for me.
const useTitle = title => {
React.useEffect(() => {
document.title = withPropsValidation(title);
}, [title]);
}
const withPropsValidation = props => {
PropTypes.checkPropTypes(propTypes, props, 'prop', '')
return props
}
const propTypes = {
title: PropTypes.string.isRequired,
}
https://github.com/facebook/prop-types#proptypescheckproptypes
In my opinion, using some kind of type mechanism would be better like TypeScript but if you don't you should still use propTypes and additionally you can check this course out from kentcdodds to be sure about propTypes usage https://egghead.io/courses/simplify-react-apps-with-react-hooks
The OP's approach has an issue in that the line const useTitle = title => { ... }; effectively makes title be the name of the parameter commonly named props. The useTitle.propTypes assignment, then, is not accurate.
The OP's approach, however, is viable (with modification). Below is an implementation showing how to use PropTypes with a custom hook:
MyHook.jsx:
import { useEffect } from 'react';
import PropTypes from 'prop-types';
const useMyHook= ({ callback, ref }) => {
...do something...
};
useMyHook.propTypes = {
callback: PropTypes.func.isRequired,
ref: PropTypes.element.isRequired
};
export { useMyHook };
MyComponent.jsx:
import React, { useRef } from 'react';
import { useMyHook} from '.../MyHook.jsx';
const MyComponent = props => {
const myRef = useRef(null);
const myCallback = () => console.log('Hello');
useMyHook({ ref: myRef, callback, myCallback });
return <div ref={myRef}>Stuff...</div>;
};
Typescript is the best way for validation and check props
import React from 'react';
const useTitle = ({title}:{title?:string})=> {
React.useEffect(() => {
document.title = title;
}, [title]);
}
export default useTitle;
Or
import React from 'react';
type customPropType= {
title?:string
}
const useTitle = ({title}:customPropType)=> {
React.useEffect(() => {
document.title = title;
}, [title]);
}
export default useTitle;
I am using jest and enzyme library for react testing with create-react-app boilerplate.
With the running of suite and test I am get in above error..
Could not found any solution yet.
Let me know if any solution.
TypeError: Cannot read property 'subroute' of undefined
Yes, I was rendering the connected component with passing the props into it.
So with that purpose all we need to pass store element into the Provider, and mount the component into it.
So all we need to understand is :
Mount: It will render the deep element of props and component associated with it.
Shallow: It will render the the first component of the top layer, not going the deep connected component as I was doing before with shallow.
Here are the code for and complete solution:
import { mountWrap } from '../contextWrap'
import { Provider } from 'react-redux'
import sinon from 'sinon'
import Login from '../components/Login/'
// import makeStore from '../redux/createStore'
import React from 'react'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const mockStore = configureMockStore([ thunk ])
const authDetails = {
'authDetails' : {
Terms :''
}
}
const match = {
params : {}
}
let actionSpy = sinon.spy()
let actionHistorySpy = sinon.stub({})
let authDetails_ = sinon.stub(authDetails)
let store
let component
/* eslint-disable */
describe('tests for MyContainerComponent', () => {
beforeEach(() => {
store = mockStore(authDetails)
component = mountWrap(<Provider store={ store }>
<Login history={actionHistorySpy} match={match} setGlobalLoaderStatus= {actionSpy} userDetail={authDetails_} />
</Provider>)
})
it('renders container', () => {
console.log(component.debug())
})
})
I currently have a component like so:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getDataAction } from ' './my-component';
export class MyComponent extends { Component } {
componentWillMount() {
this.props.getData();
}
render(){
<div>
this.props.title
</div>
}
}
const mapStateToProps = (state) => ({
title: state.title
});
const mapDispatchToProps = (dispatch) ({
getData() {
dispatch(getDataAction());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
and I am trying to shallow render test it using jest and enzyme.
test:
import React from 'react';
import { shallow } from 'enzyme';
import { MyComponent } from './index';
it('renders without crashing', () => {
shallow(<MyComponent getData={jest.fn()} />);
});
My question is, is this the conventional way to mock? Jest official docs don't mention specifically about mocking props and this post Using Jest to mock a React component with props is about testing with full mounting instead.
Is there another way to mock dispatchToProps? In this example there is only one, but what if I have a lot of functions in dispatchToProps?
Side Question: in my real file, I have a reference to a value like this.props.information.value which I expect to throw an error like cannot get value of undefined since information is not mocked/defined, but it doesn't. It's only when functions are not present that an error is thrown.
You can export mapDispatchToProps and write tests for it by importing it in your tests.
Add export { mapDispatchToProps }; at the end of your MyComponent.js
Create MyComponent.tests.js file beside MyComponent.js
import configureMockStore from 'redux-mock-store';
import thunkMiddleware from 'redux-thunk';
import { mapDispatchToProps } from './MyComponent';
const configMockStore = configureMockStore([thunkMiddleware]);
const storeMockData = {};
const mockStore = configMockStore(storeMockData);
describe('mapDispatchToProps', () => {
it('should map getDataAction action to getData prop', () => {
// arrange
const expectedActions = [getDataAction.type];
const dispatchMappedProps = mapDispatchToProps(mockStore.dispatch);
// act
dispatchMappedProps.getData();
// assert
expect(mockStore.getActions().map(action => action.type)).toEqual(expectedActions);
}
});
Here I have used thunk, just to let you know that how to do it if there are middlewares configured in your store setup.
Here getDataAction can also be a function instead of a simple action like { type: 'FETCH_DATA' } if you are using middlewares like thunks. However, the approach to test is same except that you will create expectedActions with explicit action types like const expectedActions = ['FETCH_CONTACTS']
Here FETCH_CONTACT is another action dispatched in your thunk i.e getDataAction