I'm new to unittesting Redux with Jest.
I have the following Action:
export const stepDone = (step) => (dispatch) => {
dispatch({type: STEP_DONE, payload: step});
}
How can I test this function?
Something like this should work:
//Mock the dispatch function with jest's built in mocking functions
const mockDispatch = jest.fn();
//Call the action
stepDone(999)(mockDispatch)
//Check it was called with the correct argument
expect(mockDispatch).toHaveBeenCalledWith({type: STEP_DONE, payload: 999})
The magic of Redux is that when you're testing actions and reducers you're generally just testing pure Javascript so it's not particularly complicated.
Related
I'm using Redux Toolkit, though I don't think that makes a difference.
I've set up a snackbar that reads from store.data.message, and I write the message by setting a value and then clearing the message after a timeout. This happens in a helper function, showMessage.
I call showMessage from my thunks:
export const showMessage = (dispatch: any, message: string) => {
dispatch(setMessage(message))
setTimeout(() => dispatch(clearMessage()), 3000)
}
export const fetchDataState = (): AppThunk => async dispatch => {
const state = await getSystemState()
showMessage(dispatch, 'Fetched system state.')
dispatch(getStateSucceeded(state))
}
I simply want to know if there is a way to write these without having to pass dispatch in every time I call showMessage.
Correct me if I'm wrong, but I imagine I can't write it like a thunk because redux-thunk is middleware that calls the thunks in its own way, and I'm not calling them that way.
Yes, you can write it as a thunk like this:
export const showMessage = (message: string) => (dispatch: AppDispatch) => {
dispatch(setMessage(message))
setTimeout(() => dispatch(clearMessage()), 3000)
}
// call it:
dispatch(showMessage("Hi!"))
I have the following files:
index.js
const store = createStore(...)
ReactDOM.render(<Provider store={store}><BrowserRouter><App/></BrowserRouter></Provider>, document.getElementById('root'));
The App component: (App.js)
const App = withRouter(connect(mapStateToProps, mapDispatchToProps)(Main))
export default App
So then how i can access store.dispatch inside of the Main component?
If i try to do it by store.dispatch({...}) i get:
'store' is not defined no-undef
If mapDispatchToProps looks like:
const mapDispatchToProps = dispatch => ({
myAction1: () => dispatch(myAction1())
});
connect(mapStateToProps, mapDispatchToProps)(Main)
...then in the component, you can call this.props.myAction1()
If mapDispatchToProps uses bindActionCreators:
const actions = { myAction1, myAction2 };
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
...then in the component, you can call this.props.myAction1() and this.props.myAction2()
If mapDispatchToProps is undefined:
connect(mapStateToProps)(Main)
then in the component, you can access this.props.dispatch
dispatch function should be available through this.props
this.props.dispatch()
Your component shouldn't access the store directly - connect abstracts that away.
Please see our new React-Redux docs page on connect: Dispatching Actions with mapDispatchToProps for a complete description of how to handle dispatching actions.
I've read about bindActionCreators, i've compiled a resumen here:
import { addTodo,deleteTodo } from './actionCreators'
import { bindActionCreators } from 'redux'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addTodo, deleteTodo }, dispatch)
}
*short way
const mapDispatchToProps = {
addTodo,
deleteTodo
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
another code use like this:
function mapDispatchToProps(dispatch) {
let actions = bindActionCreators({ getApplications });
return { ...actions, dispatch };
}
why previous code with bindActionCreators , don't need disptach parameter?
i've tried this way to get dispatch on this.props (but not working):
const mapDispatchToProps = (dispatch) => {
return bindActionCreators ({ appSubmitStart, appSubmitStop}, dispatch );
};
const withState = connect(
null ,
mapDispatchToProps,
)(withGraphqlandRouter);
why I had to change my old short way:
const withState = connect(
null ,
{ appSubmitStart, appSubmitStop}
)(withGraphqlandRouter);
in order to get this.props.dispatch()? because i neede to use dispatch for an isolated action creator inside a library with js functions. I mean before I don't needed use "bindActionCreators", reading this doc:
https://redux.js.org/api-reference/bindactioncreators
"The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn't aware of Redux, and you don't want to pass dispatch or the Redux store to it."
I'm importing:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
what is the difference using redux pure, and react-redux?
really I need "bindActionCreators" in my new code? because without this i can't see this.props.dispatch()
UPDATE:
I've found this solutions to get this.props.dispatch working:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators ({ appSubmitStart, appSubmitStop, dispatch }, dispatch ); // to set this.props.dispatch
};
does anyone can explain me? how i can send same distpach like a creator ?
First let's clear our minds regarding some of the key concepts here:
bindActionCreators is a util provided by Redux. It wraps each action creators to a dispatch call so they may be invoked directly.
dispatch is a function of the Redux store. It is used to dispatch actions to store.
When you use the object shorthand for mapState, React-Redux wraps them with the store's dispatch using Redux's bindActionCreators.
connect is a function provided by React-Redux. It is used to connect your component to the Redux store. When you connect your component:
It injects dispatch to your component only if you do not provide your customized mapDispatchToProps parameter.
Regarding what happened above to your code:
Component will not receive dispatch with customized mapDispatchToProps
In the code here:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ appSubmitStart, appSubmitStop, dispatch }, // a bit problematic here, explained later
dispatch
); // to set this.props.dispatch
};
You are providing your own mapDispatch, therefore your component will not receive dispatch. Instead, it will rely on your returned object to contain the action creators wrapped around by dispatch.
As you may feel it is easy to make mistake here. It is suggested that you use the object shorthand directly, feeding in all the action creators your component will need. React-Redux binds each one of those with dispatch for you, and do not give dispatch anymore. (See this issue for more discussion.)
Writing customized mapState and inject dispatch manually
However, if you do need dispatch specifically alongside other action dispatchers, you will need to define your mapDispatch this way:
const mapDispatchToProps = (dispatch) => {
return {
appSubmitStart: () => dispatch(appSubmitStart),
appSubmitStop: () => dispatch(appSubmitStop),
dispatch,
};
};
Using bindActionCreators
This is exactly what bindActionCreators does. Therefore, you can simplify a bit by using Redux's bindActionCreators:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ appSubmitStart, appSubmitStop }, // do not include dispatch here
dispatch
);
};
As mentioned above, the problem to include dispatch in the first argument is that it essentially gets it wrapped around by dispatch. You will be calling dispatch(dispatch) when you call this.props.dispatch.
However, bindActionCreators does not return the object with dispatch. It's passed in for it to be called internally, it does not give it back to you. So you will need to include that by yourself:
const mapDispatchToProps = (dispatch) => {
return {
...bindActionCreators({appSubmitStart, appSubmitStop}, dispatch),
dispatch
};
};
Hope it helped! And please let me know if anything here is unclear :)
I have made some changes to your code please try this
import * as Actions from './actionCreators'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
const mapStateToProps = (state)=>(
{
todos: state.todos
}
)
const mapDispatchToProps = (dispatch)=> (
bindActionCreators(Actions, dispatch)
)
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
New to Jest and Redux and I'm having trouble with testing functions that are dispatching to the store but don't yield a return value. I'm trying to follow the example from the Redux website does this
return store.dispatch(actions.fetchTodos()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
however I have several "fetchtodos" functions that don't return anything which causes the error TypeError:
Cannot read property 'then' of undefined due to returning undefined
I'm wondering what I can do to test that my mock store is correctly updating. Is there a way to dispatch the function, wait for it to finish and then compare the mock store with expected results?
Thanks
Edit: We're using typescript
action from tsx
export function selectTopic(topic: Topic | undefined): (dispatch: Redux.Dispatch<TopicState>) => void {
return (dispatch: Redux.Dispatch<TopicState>): void => {
dispatch({
type: SELECT_Topic,
payload: topic,
});
dispatch(reset(topic));
};
}
test.tsx
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('Select Topic action', () => {
it('should create an action to select .', () => {
const topic: Topic = mockdata.example[0];
const expectedAction = {
type: actions.SELECT_TOPIC,
payload: topic,
};
const store = mockStore(mockdata.defaultState);
return store.dispatch(actions.selectTopic(topic)).then(() => {
expect(store.getState()).toEqual(expectedAction);
});
});
});
The action is what I'm given to test(and there are many other functions similar to it. I'm getting that undefined error when running the test code, as the function isn't returning anything.
In Redux, the store's dispatch method is synchronous unless you attach middleware that changes that behavior, ie: returns a promise.
So this is likely a redux configuration problem. Be sure you are setting up your test store with the same middleware that allows you to use the promise pattern in production.
And as always, be sure to mock any network requests to avoid making api calls in test.
How can I test two simple actions in mapDispatchToProps of my component.
Command for tests that i'm using is jest --coverage, and it tells me to test next lines of my code:
export const mapDispatchToProps = (dispatch) => {
return {
----> rightText: () => dispatch(rightText()),
----> leftText: () => dispatch(leftText()),
};
};
How can I write tests to cover these two arrow functions inside mapDispatchToProps?
I think the easiest way is to pass a spy to mapDispatchToProps and then you can test the functions of returned object:
const actionProps = mapDispatchToProps(spy)
// now you can test them
actionProps.rightText()
actionProps.leftText()
rightText() and leftText() should return an object (if it's synchronous). You can also verify the action object in your spy (or it's a mock here) function.
const mockDispatch = jest.fn()
const actionProps = mapDispatchToProps(mockDispatch)
actionProps.rightText()
actionProps.leftText()
// now you can verify the actions here
mockDispatch.mock.calls[0][0]
mockDispatch.mock.calls[1][0]
And just let you know, you can pass a plain object to connect. In your case you can simply:
connect(mapStateToProps, {
rightText,
leftText
})(Component)