How to compare (anonymous) function in jest tests? - javascript

I am having the following React component connected to a redux store.
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
import { connect } from 'react-redux'
import { getWeather } from './actions/WeatherActions'
import WeatherComponent from './components/weatherComponent/WeatherComponent'
import { get } from 'lodash'
export class App extends Component {
componentDidMount () {
this.props.dispatch(getWeather())
}
render () {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<WeatherComponent
weather={{
location: get(this.props.weatherReducer.weather, 'name'),
temp: get(this.props.weatherReducer.weather, 'main.temp')
}}
/>
</div>
)
}
}
export default connect((store) => {
return {
weatherReducer: store.weatherReducer,
}
})(App)
This component is dispatching the getWeather action using the componentDidMount callback.
The getWeather action is returning an anonymous method upon resolving the axios promise.
import { GET_WEATHER_DONE, GET_WEATHER_ERROR } from './ActionTypes'
import axios from 'axios'
export function getWeather () {
let endpoint = 'http://api.openweathermap.org/data/2.5/weather?q=London&appid=2a345681ddcde393253af927097f5747'
return function (dispatch) {
return axios.get(endpoint)
.then((response) => {
return dispatch({
type: GET_WEATHER_DONE,
payload: response.data
})
})
.catch((error) => {
return dispatch({
type: GET_WEATHER_ERROR,
payload: error.response.data,
statuscode: error.response.status
})
})
}
}
No I am trying to write a unit test verifying the getWeather action is being dispatched upon mounting. This tests looks as follows and passes.
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from './actions/WeatherActions'
describe('app container', () => {
const store = configureMockStore([thunk])({
weatherReducer: {
weather: {}
}
})
const dispatchSpy = jest.fn()
store.dispatch = dispatchSpy
it('dispatches getWeather() action upon rendering', () => {
ReactDOM.render(<App store={store} />, document.createElement('div'))
expect(dispatchSpy.mock.calls[0][0].toString()).toEqual(actions.getWeather().toString())
})
})
Because of the action returning an anonymous method, I need to call the toString method upon my mock to compare the actions.
I recreated this test using snapshot testing.
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
describe('app container', () => {
const store = configureMockStore([thunk])({
weatherReducer: {
weather: {}
}
})
const dispatchSpy = jest.fn()
store.dispatch = dispatchSpy
it('dispatches correct actions upon rendering', () => {
ReactDOM.render(<App store={store} />, document.createElement('div'))
let tree = dispatchSpy.mock.calls.toString()
expect(tree).toMatchSnapshot();
})
})
Again I need to call the toString method, resulting in the following snapshot.
// Jest Snapshot v1,
exports[`app container dispatches correct actions upon rendering 1`] = `
"function (dispatch) {
return _axios2.default.get(endpoint).
then(response => {
return dispatch({
type: _ActionTypes.GET_WEATHER_DONE,
payload: response.data });
}).
catch(error => {
return dispatch({
type: _ActionTypes.GET_WEATHER_ERROR,
payload: error.response.data,
statuscode: error.response.status });
});
}"
`;
Now when running coverage, using the yarn test -- --coverage, my test is failing because of istanbul adding text to my action. The output looks as follows:
FAIL src/App.snapshot.test.js
● app container › dispatches correct actions upon rendering
expect(value).toMatchSnapshot()
Received value does not match stored snapshot 1.
- Snapshot
+ Received
-"function (dispatch) {
- return _axios2.default.get(endpoint).
- then(response => {
- return dispatch({
- type: _ActionTypes.GET_WEATHER_DONE,
- payload: response.data });
+"function (dispatch) {/* istanbul ignore next */cov_2rypo7bhf.f[1]++;cov_2rypo7bhf.s[2]++;
+ return (/* istanbul ignore next */_axios2.default.get(endpoint).
+ then(response => {/* istanbul ignore next */cov_2rypo7bhf.f[2]++;cov_2rypo7bhf.s[3]++;
+ return dispatch({
+ type: /* istanbul ignore next */_ActionTypes.GET_WEATHER_DONE,
+ payload: response.data });
- }).
- catch(error => {
- return dispatch({
- type: _ActionTypes.GET_WEATHER_ERROR,
- payload: error.response.data,
- statuscode: error.response.status });
+ }).
+ catch(error => {/* istanbul ignore next */cov_2rypo7bhf.f[3]++;cov_2rypo7bhf.s[4]++;
+ return dispatch({
+ type: /* istanbul ignore next */_ActionTypes.GET_WEATHER_ERROR,
+ payload: error.response.data,
+ statuscode: error.response.status });
- });
+ }));
}"
at Object.it (src/App.snapshot.test.js:21:18)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
The main problem I am facing is the fact that I need to call the toString method for comparison. What is the correct method for comparing (anonymous) functions in jest testing?
Full source can be found at https://github.com/wvanvlaenderen/react-redux-weathercomponent

So I was able test calls on dispatch by mocking the getWeather action in my test, and verifying the type of the return value on individual calls.
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from './actions/WeatherActions'
import { spy } from 'sinon'
describe('app container', () => {
const store = configureMockStore([thunk])({
weatherReducer: {
weather: {}
}
})
const dispatchSpy = spy(store, 'dispatch')
actions.getWeather = jest.fn().mockImplementation(() => {
return {type: 'fetching weather'}
})
it('dispatches getWeather() action upon rendering', () => {
ReactDOM.render(<App store={store} />, document.createElement('div'))
expect(dispatchSpy.firstCall.returnValue.type).toEqual('fetching weather')
})
})
Snapshot testing was achieved by rendering the call tree on the dispatch spy.
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { spy } from 'sinon'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from './actions/WeatherActions'
describe('app container', () => {
const store = configureMockStore([thunk])({
weatherReducer: {
weather: {}
}
})
const dispatchSpy = spy(store, 'dispatch')
actions.getWeather = jest.fn().mockImplementation(() => {
return {type: 'fetching weather'}
})
it('dispatches correct actions upon rendering', () => {
ReactDOM.render(<App store={store} />, document.createElement('div'))
expect(dispatchSpy.getCalls()).toMatchSnapshot();
})
})

When testing a redux thunk using Jest, i've used expect.any(Function). All assertion libs have something like this.
ex)
action
const toggleFilter = (toggle, isShown) => {
return dispatch => {
toggle === 'TOGGLE ONE'
? dispatch(toggleSwitchOne(isShown))
: dispatch(toggleSwitchTwo(isShown));
};
};
Test file:
beforeEach(() => {
store = mockStore({ showFruit: false, showVeg: false });
dispatch = jest.fn();
getState = () => store;
})
it('will dispatch action to toggle switch', () => {
let res = toggleFilter(type, isShown)(dispatch, getState);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith(expect.any(Function));
});

Related

How to dispatch an action from inside getInitialProps?

I am trying to implement Redux in a Next.js app and have problems getting the dispatch function to work in getInitialProps. The store is returned as undefined for some reason that I cannot figure out. I am using next-redux-wrapper. I have followed the documentation on next-redux-wrapper GitHub page but somewhere on the way it goes wrong. I know the code is working - I used axios to directly fetch the artPieces and then it worked just fine but I want to use Redux instead. I am changing an react/express.js app to a Next.js app where I will use the API for the basic server operations needed. This is just a small blog app.
Here is my store.js:
import { createStore } from 'redux';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
// create your reducer
const reducer = (state = { tick: 'init' }, action) => {
switch (action.type) {
case HYDRATE:
return { ...state, ...action.payload };
case 'TICK':
return { ...state, tick: action.payload };
default:
return state;
}
};
// create a makeStore function
const makeStore = (context) => createStore(reducer);
// export an assembled wrapper
export const wrapper = createWrapper(makeStore, { debug: true });
And here is the _app.js:
import './styles/globals.css';
import { wrapper } from '../store';
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default wrapper.withRedux(MyApp);
And finally here is where it does not work. Trying to call dispatch on the context to a sub component to _app.js:
import React from 'react';
import { ArtPiecesContainer } from './../components/ArtPiecesContainer';
import { useDispatch } from 'react-redux';
import axios from 'axios';
import { getArtPieces } from '../reducers';
const Art = ({ data, error }) => {
return (
<>
<ArtPiecesContainer artPieces={data} />
</>
);
};
export default Art;
Art.getInitialProps = async ({ ctx }) => {
await ctx.dispatch(getArtPieces());
console.log('DATA FROM GETARTPIECES', data);
return { data: ctx.getState() };
};
This should probably work with "next-redux-wrapper": "^7.0.5"
_app.js
import { wrapper } from '../store'
import React from 'react';
import App from 'next/app';
class MyApp extends App {
static getInitialProps = wrapper.getInitialAppProps(store => async ({Component, ctx}) => {
return {
pageProps: {
// Call page-level getInitialProps
// DON'T FORGET TO PROVIDE STORE TO PAGE
...(Component.getInitialProps ? await Component.getInitialProps({...ctx, store}) : {}),
// Some custom thing for all pages
pathname: ctx.pathname,
},
};
});
render() {
const {Component, pageProps} = this.props;
return (
<Component {...pageProps} />
);
}
}
export default wrapper.withRedux(MyApp);
and Index.js
import { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { END } from 'redux-saga'
import { wrapper } from '../store'
import { loadData, startClock, tickClock } from '../actions'
import Page from '../components/page'
const Index = () => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(startClock())
}, [dispatch])
return <Page title="Index Page" linkTo="/other" NavigateTo="Other Page" />
}
Index.getInitialProps = wrapper.getInitialPageProps(store => async (props) => {
store.dispatch(tickClock(false))
if (!store.getState().placeholderData) {
store.dispatch(loadData())
store.dispatch(END)
}
await store.sagaTask.toPromise()
});
export default Index
For the rest of the code you can refer to nextjs/examples/with-redux-saga, but now that I'm posting this answer they're using the older version on next-redux-wrapper ( version 6 ).

How to mock useLocation correctly?

I have a component that uses useLocation hook to get the path from the URL.
const { pathname } = useLocation();
useEffect(() => { }, [pathname]);
While I am trying to mock the location using ,
import React from 'react';
import ExampleComponent from './ExampleComponent';
import { fireEvent, render } from '#testing-library/react';
import { shallow } from 'enzyme';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: () => ({
pathname: 'https://URL/'
})
}));
describe('<ExampleComponent />', () => {
it('should render correctly', () => {
shallow(<ExampleComponent />);
});
});
I am getting this error while I run the test,
TypeError: Cannot read property 'location' of undefined
Try mocking the useLocation as jest.fn().mockImplementation
jest.mock('react-router', () => ({
...jest.requireActual("react-router") as {},
useLocation: jest.fn().mockImplementation(() => {
return { pathname: "/testroute" };
})
}));
Below is how I have done this in my tests. * Note I am using typescript
import routeData from 'react-router';
describe('Login Page UnitTests', () => {
const useLocation = jest.spyOn(routeData, 'useLocation');
beforeEach(() => {
useLocation.mockReturnValue({ search: 'testQueryParameters'} as any);
});
// Add unit tests
}
Ensure that you clear the mock to avoid issue with data in subsequent tests
The correct way to mock useLocation is below:
import React from 'react';
import ExampleComponent from './ExampleComponent';
import { fireEvent, render } from '#testing-library/react';
import { MemoryRouter} from 'react-router-dom';
import { shallow } from 'enzyme';
const renderComponent = () => {
return (
<MemoryRouter
initialEntries={["/one", "/two", { pathname: 'https://URL/' }]}
initialIndex={1}>
<ExampleComponent />
</MemoryRouter>
);
}
describe('<ExampleComponent />', () => {
it('should render correctly', () => {
shallow(renderComponent());
});
});

"TypeError: dispatch is not a function" when using useReducer/useContext and React-Testing-Library

I'm having issues testing my components that use dispatch via useReducer with React-testing-library.
I created a less complex example to try to boil down what is going on and that is still having the same dispatch is not a function problem. When I run my tests, I am getting this error:
11 | data-testid="jared-test-button"
12 | onClick={() => {
> 13 | dispatch({ type: 'SWITCH' })
| ^
14 | }}
15 | >
16 | Click Me
Also, if I do a console.log(typeof dispatch) inside RandomButton, and I click on the button the output says function.
Here is the test in question.
import React from 'react'
import RandomButton from '../RandomButton'
import { render, fireEvent } from '#testing-library/react'
describe('Button Render', () => {
it('click button', () => {
const { getByTestId, queryByText } = render(<RandomButton />)
expect(getByTestId('jared-test-button')).toBeInTheDocument()
fireEvent.click(getByTestId('jared-test-button'))
expect(queryByText('My name is frog')).toBeInTheDocument()
})
})
Here is my relevant code:
RandomButton.js
import React, { useContext } from 'react'
import MyContext from 'contexts/MyContext'
const RandomButton = () => {
const { dispatch } = useContext(MyContext)
return (
<div>
<Button
data-testid="jared-test-button"
onClick={() => {
dispatch({ type: 'SWITCH' })
}}
>
Click Me
</Button>
</div>
)
}
export default RandomButton
MyApp.js
import React, { useReducer } from 'react'
import {myreducer} from './MyFunctions'
import MyContext from 'contexts/MyContext'
import RandomButton from './RandomButton'
const initialState = {
blue: false,
}
const [{ blue },dispatch] = useReducer(myreducer, initialState)
return (
<MyContext.Provider value={{ dispatch }}>
<div>
{blue && <div>My name is frog</div>}
<RandomButton />
</div>
</MyContext.Provider>
)
export default MyApp
MyFunctions.js
export const myreducer = (state, action) => {
switch (action.type) {
case 'SWITCH':
return { ...state, blue: !state.blue }
default:
return state
}
}
MyContext.js
import React from 'react'
const MyContext = React.createContext({})
export default MyContext
It is probably something stupid that I am missing, but after reading the docs and looking at other examples online I'm not seeing the solution.
I've not tested redux hooks with react-testing-library, but I do know you'll have to provide a wrapper to the render function that provides the Provider with dispatch function.
Here's an example I use to test components connected to a redux store:
testUtils.js
import React from 'react';
import { createStore } from 'redux';
import { render } from '#testing-library/react';
import { Provider } from 'react-redux';
import reducer from '../reducers';
// https://testing-library.com/docs/example-react-redux
export const renderWithRedux = (
ui,
{ initialState, store = createStore(reducer, initialState) } = {},
options,
) => ({
...render(<Provider store={store}>{ui}</Provider>, options),
store,
});
So, based upon what you've shared I think the wrapper you'd want would look something like this:
import React from 'react';
import MyContext from 'contexts/MyContext';
// export so you can test that it was called with specific arguments
export dispatchMock = jest.fn();
export ProviderWrapper = ({ children }) => (
// place your mock dispatch function in the provider
<MyContext.Provider value={{ dispatch: dispatchMock }}>
{children}
</MyContext.Provider>
);
and in your test:
import React from 'react';
import RandomButton from '../RandomButton';
import { render, fireEvent } from '#testing-library/react';
import { ProviderWrapper, dispatchMock } from './testUtils';
describe('Button Render', () => {
it('click button', () => {
const { getByTestId, queryByText } = render(
<RandomButton />,
{ wrapper: ProviderWrapper }, // Specify your wrapper here
);
expect(getByTestId('jared-test-button')).toBeInTheDocument();
fireEvent.click(getByTestId('jared-test-button'));
// expect(queryByText('My name is frog')).toBeInTheDocument(); // won't work since this text is part of the parent component
// If you wanted to test that the dispatch was called correctly
expect(dispatchMock).toHaveBeenCalledWith({ type: 'SWITCH' });
})
})
Like I said, I've not had to specifically test redux hooks but I believe this should get you to a good place.

React js, Cannot read property "0". When calling an API

I cannot able to access the data from the fetch function. I want to pass the data from action to reducer. API is called using an fetch function, api is returned in the form of promise. So, API is called separately and data is returned back to the action payload.
import { INDEX_PRESCRIPTION } from '../constant.js';
function fetch_prescription(){
const base_url= "http://192.168.1.22:3000/api/v1/";
const fetch_url = `${base_url}/prescriptions`;
let datas = [];
return fetch(fetch_url, {
method: "GET"
})
.then(response => response.json())
.then(data => {
datas.push(data['prescriptions'])
return datas
})
}
export const indexPrescription = async () => {
const action = {
type: INDEX_PRESCRIPTION,
details: await fetch_prescription()
}
return action;
console.log('action details', action.details)
}
export const getIndexPrescription = (dispatch) => {
dispatch(indexPrescription());
}
On examining the console, we get:
How to access the prescription details. I tried to access it by action.details["0"]["0"] , but results in 'Cannot read property "0" of undefined '. I have gone through many questions and solution related to this problem, but cant able to study what is going wrong with my code.
Update Here is my index.jsx component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { getIndexPrescription } from '../actions/index.js';
class Index extends Component {
constructor(props){
super(props);
this.state = {
prescription: null
}
}
componentWillMount(){
this.props.getIndexPrescription();
}
render(){
return(
<h2>
Prescription Index
</h2>
)
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({ getIndexPrescription }, dispatch)
}
function mapStateToProps(state){
return {
prescription: state
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Index);
And My src/index.js file is
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import {Provider} from 'react-redux';
import reducer from './reducers';
import Index from './components/index.jsx';
const store = createStore(reducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<Index />
</Provider>, document.getElementById("root")
)
Your promise is resolved only after you have answer from the server. You need to use additional layer in order to handle async behavior in redux.
For example with redux-thunk, you can make it work like this:
import { INDEX_PRESCRIPTION } from '../constant.js';
function fetch_prescription(){
const base_url= "http://192.168.1.22:3000/api/v1/";
const fetch_url = `${base_url}/prescriptions`;
let datas = [];
return fetch(fetch_url, {
method: "GET"
})
.then(response => response.json())
.then(data => data['prescriptions']);
}
export const indexPrescription = (dispatch) => {
fetch_prescription()
.then(details => {
const action = {
type: INDEX_PRESCRIPTION,
details
}
dispatch(action);
}
}
The part you are missing here is that the function fetch_prescription() is asynchronous. So the data may not be available when you are accessing the data.
You are returning the datas before resolving the asnyc function return datas
You may use it as
import { INDEX_PRESCRIPTION } from '../constant.js';
function fetch_prescription(){
const base_url= "http://192.168.1.22:3000/api/v1/";
const fetch_url = `${base_url}/prescriptions`;
let datas = [];
return fetch(fetch_url, {
method: "GET"
})
.then(response => response.json())
.then(data => {
datas.push(data['prescriptions'])
return datas
})
}
export const indexPrescription = async () => {
const action = {
type: INDEX_PRESCRIPTION,
details: await fetch_prescription()
}
return action;
}
export const getIndexPrescription = (dispatch) => {
dispatch(indexPrescription());
}
And dispatch the above action where ever you want.
Call getIndexPrescription() in componentWillMount
Find the code below to add redux-thunk to your application.
...
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
...
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);
<Provider store={store}>
...
</Provider>

Redux firing undefined action while using redux thunk

This issue likely stems from a misconfiguration of redux-thunk or a misunderstanding of how to write a thunk. I've tried a lot of different ways, but from what I can tell, this should work. However, I'm still getting a console message that says its firing a redux action of undefined.
Here is my store configuration
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import App from './components/App';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('rootElement')
);
Here is my action:
import axios from 'axios';
export const GET_ABOUT_CONTENT_REQUEST = 'GET_ABOUT_CONTENT_REQUEST';
export const GET_ABOUT_CONTENT_FAILED = 'GET_ABOUT_CONTENT_FAILED';
export const GET_ABOUT_CONTENT_OK = 'GET_ABOUT_CONTENT_OK';
export const fetchAboutContent = () => {
const url = `http://localhost:3000/about`;
return (dispatch, getState) => {
if (getState.isInitialized === true){
console.log("desktop init should not be called when already desktop is init")
return Promise.resolve();
}
if (getState.about.isLoading) {
console.log('is loading');
return Promise.resolve();
}
dispatch({ type: GET_ABOUT_CONTENT_REQUEST });
axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));
}
}
Here is me firing the action in my component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../../actions/about';
import getAboutContent from '../../reducers';
class AboutMe extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.getAboutContent();
}
render() {
return <div>{ this.props.content }</div>
}
}
const mapStateToProps = (state) => ({
content: {} || getAboutContent(state)
})
const mapDispatchToProps = (dispatch) =>
bindActionCreators({ getAboutContent }, dispatch)
export default connect(
mapStateToProps, mapDispatchToProps
)(AboutMe);
I've tried quite a few configurations for mapDispatchToProps, i.e. connect(..., { fetchData: getAboutContent })..., and more. Any help is greatly appreciated.
Edit:
Here is the git repo, if that is helpful to anybody: https://github.com/sambigelow44/portfolio-page
Check your reducer name,you export fetchAboutContent, but import getAboutContent.
Code in action file is seems to be incorrect.
getState is a function.
const state = getState();
Change below code.
import axios from 'axios';
export const GET_ABOUT_CONTENT_REQUEST = 'GET_ABOUT_CONTENT_REQUEST';
export const GET_ABOUT_CONTENT_FAILED = 'GET_ABOUT_CONTENT_FAILED';
export const GET_ABOUT_CONTENT_OK = 'GET_ABOUT_CONTENT_OK';
export const fetchAboutContent = () => {
const url = `http://localhost:3000/about`;
return (dispatch, getState) => {
if (getState().isInitialized === true){
console.log("desktop init should not be called when already desktop is init")
return Promise.resolve();
}
if (getState().about.isLoading) {
console.log('is loading');
return Promise.resolve();
}
dispatch({ type: GET_ABOUT_CONTENT_REQUEST });
axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));
}
}
Also you need to return promise from axios call, just add return statement.
return axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));

Categories