I want to check if dispatch by react-redux has been called.
In debug mode, I can see that the respective line of code is executed, so dispatch is actually called but in my jest test the result is always 0 calls.
What would be the proper way to do this?
const mockDispatchFn = jest.fn();
jest.doMock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: () => mockDispatchFn,
}));
...
expect(mockDispatchFn).toHaveBeenCalledWith(...);
doMock and requireActual are not really reliable especially for things that might already be caught by other modules in their constants or through function closures. Better to avoid it instead of searching how to make it working.
Option 1. Better one.
Redux docs are pretty opinionated here:
Prefer writing integration tests with everything working together. For a React app using Redux, render a <Provider> with a real store instance wrapping the components being tested. Interactions with the page being tested should use real Redux logic, with API calls mocked out so app code doesn't have to change, and assert that the UI is updated appropriately.
It means:
Real store reducer
to mock store data we will call dispatch on that story with related action creators mockedStore.dispatch(someDataLoadedAction(someMockedData))
instead of checking whether dispatch has been called we, depending on what should happen, will:
a. either validate new store state through selectors("after clicking this button in Component1 we expect Redux's selector getSomeFlag to return true") b. or validating something else happened(network call has been sent with expected parameters, document title has been changed, navigation has occured etc)
c. or validating our component has been updated according to new store's state
Something like this:
const store = createStore(reducer, applyMiddleware(thunk));
// store preparation
store.dispatch(somethingLoadedSuccess(mock1));
store.dispatch(somethingChanged(someMock2));
// rendering component
const { getByRole, getByText } = render(<Provider store={store}>
<ComopnentUnderTest prop1="a" prop2={something} />
</Provide>);
// simulating some scenario
fireEvent.click(getByRole('button', { name: 'Save' }));
// asserting
expect(someSelector1(store.getState())).toBe(42);
expect(getByText('Saved successfully')).toBeInTheDocument();
expect(mockedNetworkLayer.isDone()).toBe(true);
Option 2. Tricky and not that reliable.
Using redux-mock-store you can easily check which actions has been dispatched, however with this approach:
you cannot change Redux state during the test so will be unable to test some cases
you will need to mock store tree with static data; tests become fragile - if you ever do internal refactoring of store's structure and update correctly all the selectors, you will also need to update all the mocked static data across the tests to make them passing again; tests become unreliable - if you ever do internal refactoring of store's structure and miss to update selectors accordingly, tests will still be passing though app will be broken
// store preparation
const store = configureStore([thunk])(staticDataMockForAStore);
// rendering component
const { getByRole, getByText } = render(<Provider store={store}>
<ComopnentUnderTest prop1="a" prop2={something} />
</Provide>);
// simulating some scenario
fireEvent.click(getByRole('button', { name: 'Save' }));
// asserting
expect(store.getActions()).toContainEqual({
type: 'SOME_ACTION_TYPE',
value1: 1,
value2: 'a'
});
Also for that approach to assert on actions dispatched you may need expect.objectContaining to ignore some properties with random data(UUID etc)
Related
Can someone explain to me what is the real difference and why both of the example here are working the same:
1) Change a state of observable via action/runInAction inside the store file:
colorStore file:
#observable
color='red'
#action
setColor(){
this.color='blue'
}
2)Change the state via the component itself (which assumed to be bad practice):
React Component file:
onClick = () => this.props.colorStore.color='blue' //still working...
Mobx action is doing the batching, similarly to how ReactJS batches multiple changes.
When you use reactjs click handler react is automatically batching changes that happen inside it, so you will not see component rendering multiple times, however, if you call setColor from some other event, let's say after loading some data, and have multiple calls to change the observable inside the setColor that will trigger the observer three times, and the component will be rendered three times.
When you wrap your function with #action decorator or you use runInAction function, only the last value will be used (green in the code below) and the component will be rendered only once.
setColor(){
// this will render three times
this.color='blue'
this.color='red'
this.color='green'
}
vanilla mobx example that reacts only once:
import { runInAction, observable, reaction, toJS } from "mobx";
const test = observable({
name: "sonic",
nick: "speed demon"
});
// console.log will be called only once with the value "name: 3"
reaction(
() => toJS(test),
data => console.log(`name: ${data.name}`)
);
runInAction(() => {
test.name = "1";
test.name = "2";
test.name = "3";
});
view on codesandbox
Also check out the discussion on the github repo: is #action really necessary?
The difference is more related to conventions and writing clean maintainable code than in the behavior of the program. So:
You are mutating store data in your UI component. This is not correct, because the UI should only visualize data and handle for example user actions which are then translated to certain action in the data store(in your case updating a color). It's store responsibility to manage the state of this data
All observable data is considered a good practice to be mutated only in actions. Why? Because the single source of mutating data is clear - only in actions and only in a data layer(the store). The code is less prone to errors and unclear state management and the great benefit is that you can enforce your application not to build if you turn on use strict mode
I am using react, redux, and redux-thunk have a set of queries I have to run in the "logic layer". The results of each "action" call determine the path I take, so I am actually awaiting the promises returned from the dispatch call to the redux-thunks.
const handleClick = async (args) => {
let foo = await dispatch(fetchFoo(args));
if(foo) { do something... }
else {
let bar = await dispatch(fetchBar(args));
if(bar) { do another thing... }
else { or do another thing... }
}
};
Example thunk:
export const fetchFoo = args => async (dispatch) => {
let foo = await api.fetchFoo(args);
dispatch(fetchSuccess(foo));
// !!!!!!!!!!
return foo;
// !!!!!!!!!!
}
If I don't do this, it's pretty awkward to wait until a re-render (maybe) puts "foo" in the redux state prop, then wait again until a re-render (maybe) puts "bar" in the redux state, etc...
I've never really seen this pattern before although I have seen awaiting void promises return from thunks.
Is it acceptable to return the value from the redux-thunk action and use it rather than getting the values from a redux state selector? This seems to break the rules of the "single source of truth." If not, what do I do?
Using this approach it would be much harder to refactor state and change flow how data is loaded. Say in case requirements are changed and fetchBar() should be called by interval or need to be loaded by parent and passed through props. Or you want to throttle loading. Or cache data for some time.
I used this pattern with Promise returned and found it makes things rather complicated. The only advantage I found: I didn't need having isLoading or loadingError flags in redux storage to know if data is already loaded or not.
But also it means I have to call some methods redundantly just to retrieve brand new Promise await for - since promises can be resolved only once - in order to wait until some data loaded. And once some action triggers few API calls in sequence I ended with awaiting for data I did not even need.
In opposite if we rely only on data in redux it would be as easy as const isStillLoading = props.isData1Loading && props.isData2Loading
Consider moving condition for loading data into action itself. If you find API calls sequence becomes too complex you may switch to redux-saga or redux-loop which both provide better control over execution flow.
Problem
public componentDidMount() {
// after component mounts I want to get updates from RX source
const that = this;
SomeExternalObservable.subscribe((value) => {
that.setState((prev, props) => ({
currentValue: value
});
});
}
In some cases, I get warning Cannot update during an existing state transition” React with Observable.
I checked SO for answers to that problem, but most of them just suggested to move my code to componentDidMount.
What happens here
it's not obvious, but subscription on observable can be executed (it's very likely to) synchronously (see here https://github.com/tc39/proposal-observable/issues/38)
it's even more likely if an observable has already set value (like BehaviourSubject - see tip here BehaviorSubject vs Observable?)
The problem occurred as well when using libraries like https://github.com/jayphelps/react-observable-subscribe.
An alternative would be to use props instead of setState.
For managing React state with Rx I prefer to use the mapPropsStream recompose helper. It can be a bit confusing if you don't tend to use HOCs, but it is actually pretty simple.
const withSomeExternalValue = mapPropsStream(props$ =>
Observable.combineLatest(
props$,
someExternal$.startWith(null),
(props, someExternalValue) => ({...props, someExternalValue})
)
);
const MyEnhancedComponent = withSomeExternalValue(MyComponent);
MyEnhancedComponent is a new Component type. When it will mount, it will also subscribe to the external Observable. Any emitted value will render the component with the new value as prop.
Additional explanation and notes:
The props$ is an Observable of props. Any change of a prop will result in a new item emitted for prop$
The returned Observable is a new props Observable that its emitted items will be used to render the enhanced component.
The .startWith(null), is to make sure the enhanced component will be rendered before the first value of someExternal$ is emitted. In this case, the someExternalValue prop will start with null
withSomeExternalValue HOC can be reused if these external values are needed by another component. withSomeExternalValue(MyAnotherComponent)
The mapPropsStream can be used with other Observable implementation - so it should be configured. Here is how to use it with Rx -
Can be configured globally:
import rxjsconfig from 'recompose/rxjsObservableConfig'
setObservableConfig(rxjsconfig)
Or by using mapPropsStreamWithConfig:
import rxjsConfig from 'recompose/rxjsObservableConfig'
const mapPropsStream = mapPropsStreamWithConfig(rxjsConfig)
I post the question with the answer as it seems allowed, supported
here
https://meta.stackexchange.com/questions/17463/can-i-answer-my-own-questions-even-if-i-knew-the-answer-before-asking;
spent some time to find the issue and couldn't find similar SO post,
so I hope it will help someone.
Solution
Because we can't expect the subscription to be triggered asynchronously working solution is to force it, i.e. by using Scheduler:
public componentDidMount() {
// after component mounts I want to get updates from RX source
const that = this;
SomeExternalObservable
.subscribeOn(Scheduler.asap) // here
.subscribe((value) => {
that.setState((prev, props) => ({
currentValue: value
});
});
}
Why
componentDidMount happens directly after rendering and change to state will cause additional render - which can happen in the same tick (https://reactjs.org/docs/react-component.html#componentdidmount)
setState should work asynchronously, but doesn't have to; React will decide about it at runtime (https://reactjs.org/docs/react-component.html#setstate)
Subscription on Observable by default is synchronous.
Wrap up question
Is there React point of view right way to fix that issue, different than solution presented?
Would there be anything wrong/anti-pattern-ish (in terms of 'thinking-in-react/redux') in added a callback to the action.data passed into an action?
// reducer
ACTION_FOR_REDUCER() {
var x = 123
if ( action.data.callback ) action.data.callback( x )
return {
something: action.data.somedata
}
},
Then accessing that data later in the App when the action is called (in an container perhaps)
// later in the app
this.props.dispatch(changeSomething({
somedata: somedata,
callback: (x) => { console.log(x) }
}))
The idea is not wrong, the implementation is.
Instead of adding a callback, return a promise from the action (you will need redux-thunk middleware or some similar alternative).
Then you can simply do:
dispatch(myAction).then(callback);
Of course, you can also simply dispatch another action in your callback, which usually end up being one compound action.
const myAction = ...
const callbackAction = ...
const compoundAction = () => dispatch => {
dispatch(myAction())
.then(() => dispatch(callbackAction()));
};
The third principle of Redux (listed in the introduction section of the docs) is that 'changes are made with pure functions'.
A pure function is a function that always returns the same result given the same inputs, and doesn't cause any side effects. Having a callback log something out most definitely would be a side effect!
The docs go on to say:
It's very important that the reducer stays pure. Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
The reasoning behind this is it makes your reducers predictable - you know that, given an action with a certain payload, you're guaranteed to have the same output. This makes your app both easier to understand as a developer, and easier to unit test.
If you want to perform side effects when an action takes place, you should install a middleware that allows you to do so - some common examples of these are:
redux-thunk, which allows you to dispatch functions that can in turn dispatch actions (commonly used to make API calls)
redux-logger, which allows you to log out actions that are dispatched.
redux-devtools-extension, which allows you to view your state and actions in a Chrome devtools extension.
If you can't find a middleware package that does what you need (rare, to be honest - there's lots out there already!), you can write your own quite easily.
Callbacks introduce side-effects, so when using Redux you should consider returning a new state with the result, for example with done: true flag and react to that in componentDidUpdate.
For managing asynchrony and effects you can use redux-saga, redux-thunk, RxJs or another library.
If you want to stick with callbacks, why even bother with Redux?
I'm new to React and Redux and I'm trying to write a simple application where a person can submit a URL for an image and it will show up on the page. Note that there is no backend to the application as of yet.
export const addImage = (url) => {
return {
type: ADD_IMAGE,
key: Guid.create().toString(),
payload: url
}
}
Adding an image creates an action of type ADD_IMAGE and my reducer updates the state consequently. However I also check if the URL is already in the list.
switch (action.type) {
case ADD_IMAGE:
if (state.find(image => image.url === action.payload)) {
return state;
} else {
return(
[
...state,
{key: action.key, url: action.payload}
]
);
}
break;
default:
}
The problem is that when I deny a post because the URL is already in the state I also want to convey that message to the user by showing it in a div next to the form. From what I've read I think I'm not supposed to try to access React state from reducers (if that is even possible) and... well.. I'm just stuck. I've been trying to find a simple guide on how to do this but I find nothing I can quite understand. After adding a database I guess I will have to do this as part of the async process but as I have it now I guess there should be some kind of simple solution.
You are starting to introduce logic into your reducer and this will inevitably lead to situation where you need to process some state outside of the reducer's scope.
The solution is to transfer your reducer logic into a thunk using a middleware package such redux-thunk (or similar package). This allows you to treat special kinds of actions as functions which means you can extend a plain action with specific action-related logic. The example you give of needing to dispatch an error action under certain conditions is an excellent use-case for redux-thunk.
Below is a example of how you might pull the logic out of your reducer into a thunk. You should note that, unlike reducers, thunks explicitly support fetching state and dispatching subsequent actions via the getState and dispatch functions.
Thunk example
export const addImage = (url) => {
return (dispatch, getState) => {
const key = Guid.create().toString()
dispatch({
type: ADD_IMAGE,
key,
payload: url
})
const state = getState()
// you would want to use a `selector` here to locate the existing image
// within the state tree
const exists = selectors.images.exists(state, url)
if (exists) {
dispatch(actions.ERROR_IMAGE_EXISTS({key, url}))
}
}
}
A note on selectors
You will see that I am using a selector to determine if the image exists. In the same way that thunks are the place to put your dispatch logic, a selector is the place to put your state-traversal logic. They are used to return portions of the state-tree or provide simple state-utilities such as the exists function shown above. Packages are available to help, for example reselect.
Follow on questions from comments
Are selectors not a built-in thing in Redux?
No they are not. Selectors are an idea that builds on top of redux and the concept exists as a place to put your state searching, caching, reading logic. This extracts the sometimes complex state traversal logic out of your thunks and components and into a nice tidy, structured collection of selectors.
Why use a selector instead of state.images.find(i => i.url === url)?
If you use a selector package then you get far more benefit than just a good separation of concerns, you get a big performance improvement (see usage example below).
Here are the headlines from the popular reselect package:
Selectors can compute derived data, allowing Redux to store the minimal possible state.
Selectors are efficient. A selector is not recomputed unless one of its arguments change.
Selectors are composable. They can be used as input to other selectors.
Why doesn't actions.ERROR_IMAGE_EXISTS(url) work for me
Because I just made that up for the example. The point is that you can dispatch actions from within the thunk, how you declare or get access to the action is up to you. I tend to centralise all my shared actions into an actions object that I import.
Selector usage example
Here is an example from my real-life code that shows how I use selectors to passing portions of the state as props to a react component:
const mapStateToProps = (state) => ({
model: services.model.selector.getSelected(state),
build: services.build.selector.getLastBuild(state),
recommendations: services.recommend.selector.getRecommendations(state)
})
Each of these selectors is finding the correct portion of the state tree and delivering it back ready for use. Nice and tidy, and if you use reselector, very efficient.