I am trying to test my actions in my React / Redux application, and in one of my actions, I am using getAppState() to get the current state of Redux. The action has no parameters, and simply deconstructs state properties from the return of getAppState()
actions/myFile/index.test.js
it('should call myAction successfully', () => {
const expected = [
{
type: MY_TYPE,
payload: {
...mockPayload
}
}
];
store.dispatch(myAction());
expect(store.getActions().toEqual(expected));
});
actions/myFile/index.js
export const myAction = () => (dispatch, getAppState) => {
const { myReducer: { reducerProp } } = getAppState();
const { valOne, valTwo, valThree } = reducerProp;
return myServiceCallPromise({ valOne, valTwo }, valThree)
.then((res) => {
dispatch(anotherAction());
});
}
When trying to test my action with Jest, I'm getting an error stating:
TypeError: Cannot read property 'reducerProp' of undefined
My question is HOW do I mock what the getAppState() func returns and use them in my test? I have googled once, saw the results and figured, why not go to StackOverflow and ask myself lol
(I'm also assuming you are using redux-thunk).
You are going to need to configure your store:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const initialState = {
myReducer: { reducerProp: 'something here' }
};
const store = mockStore(initialState)
// Your code down here...
See here for more info about how to use the store.
Related
I am pretty new to redux and here I am trying to create a common dispatch function where I can call the function from multiple components but can't seem to use useDispatch() in my common component getting invalid hook call error.
import { useDispatch, useSelector } from "react-redux";
import { UPDATE_PREVIEW_DATA } from "../../redux/types";
export default function setPreviewData(event, obj, lang) {
const dispatch = useDispatch();
const previewData = useSelector((state) => state.previewData);
const dispatchFunc = () => {
dispatch({
type: UPDATE_PREVIEW_DATA,
data: {
[obj]: {
[lang]: {
...previewData[obj][lang],
[event.target.name]: event.target.value,
},
},
},
});
};
return dispatchFunc;
}
// previewData.js in action folder
import { UPDATE_PREVIEW_DATA } from "../types";
const previewData = (data) => (dispatch) => {
dispatch({
type: UPDATE_PREVIEW_DATA,
data,
});
};
export default previewData;
// previewData.js in reducers folder
import { UPDATE_PREVIEW_DATA } from "../types";
const initialState = {...};
const previewData = (state = initialState, action) => {
switch (action.type) {
case UPDATE_PREVIEW_DATA: {
return action.data;
}
default:
return state;
}
};
export default previewData;
And I am trying to make this work like
// component.jsx
setPreviewData(e, "hightlights", "en");
Hooks are intended to be used in Functional components only. As per the Rules of hooks they can be called from
React function components.
Custom Hooks
Reference -> https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions
now you might think your setPreviewData is a React Function Component, but it's just a normal js function, that's why you are getting the error.
As a result, it doesn't get wrapped in React.createElement, so it thinks the hook call is invalid.
Moreover, you are committing one more mistake here, lets's say if setPreviewData was a Function Component you still call it as if though its a normal function
I want to write unit test which checks if data (onLoad) from dispatching async thunk is delivered into state.
It's first time when i'm writing unit tests and it's black magic for me.
My solution it's not working because my state is always empty object.
My component has following logic:
useEffect(() => {
dispatch(getProducts({ filters: false }));
}, [dispatch, filters]);
Here is what i've tried:
import {
render as rtlRender,
screen,
fireEvent,
cleanup,
} from "#testing-library/react";
import { Provider } from "react-redux";
import { store as myStore } from "store/root-reducer";
import ProductList from "components/product/product-list/product-list";
import { productSlice } from "store/slices/product/product.slice";
describe("Product components", () => {
const renderWithRedux = (component) =>
rtlRender(<Provider store={myStore}>{component}</Provider>);
const thunk =
({ dispatch, getState }) =>
(next) =>
(action) => {
if (typeof action === "function") {
return action(dispatch, getState);
}
return next(action);
};
const create = () => {
const store = {
getState: jest.fn(() => ({})),
dispatch: jest.fn(),
};
const next = jest.fn();
const invoke = (action) => thunk(store)(next)(action);
return { store, next, invoke };
};
const initialState = {
product: null,
products: [],
errorMessage: "",
isFetching: false,
filters: false,
};
it("should render product list after dispatching", async () => {
renderWithRedux(<ProductList />);
const { store, invoke } = create();
invoke((dispatch, getState) => {
dispatch("getProducts"); // i want to dispatch asyncthunk which is called getProducts()
getState();
});
expect(store.dispatch).toHaveBeenCalledWith("getProducts");
expect(store.getState).toHaveBeenCalled();
});
});
Can you include your error message?
I think you're almost there. Since you're rendering an actual component and redux store, you're actually doing an integration-style test. My guess is that your dispatch to getProducts() is firing correctly, but the state isn't updating in your test because you haven't mocked an API response. See how msw is being used to do this in the Redux Testing doc
When testing action creators, I want to test whether the correct action creator was called and also whether the right action was returned
action.js
export const AboutUs = {
getAboutUsContentSuccess: 'getAboutUsContentSuccess/AboutUs',
getBuildVersionSuccess: 'getBuildVersionSuccess/AboutUs'
};
export const getAboutUsContentSuccess = (data) => {
return {
type: AboutUs.getAboutUsContentSuccess,
data
}
}
action.test.js
import * as actions from './actions'
describe('actions', () => {
it('should create an action to getAboutUsContent', () => {
const text = 'Finish docs'
const expectedAction = {
type: 'getAboutUsContentSuccess/AboutUs',
text
}
console.log(expectedAction)
console.log(actions.getAboutUsContentSuccess(text))
expect(actions.getAboutUsContentSuccess(text)).toEqual(expectedAction)
})
})
But I am getting the following error
TypeError: Cannot read property 'getAboutUsContentSuccess' of undefinedd
It seems that you have a typo in your imports, the name of your file action.js is singular.
Just change your imports to: import * as actions from './action'
Following is my action creator with multiple dispatches
export function getAnalysisById(Id){
let url = APIEndpoints["getAnalysisById"];
var dataObject = {
id: Id
}
return dispatch => {
dispatch ({
type:LOADING_ANALYSIS,
payload : { isLoading : true}
});
const request = axios.post(url, dataObject);
dispatch ({
type:GET_ANALYSIS_BY_ID,
payload: request
});
};
}
Now following is the testing code written for the above action
var chai = require('chai');
var expect = chai.expect;
const actions = require('../../src/actions/index');
import { applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import nock from 'nock'
import ReduxPromise from 'redux-promise'
const middlewares = [ thunk,ReduxPromise ]
import configureStore from 'redux-mock-store'
const mockStore = configureStore(middlewares)
describe("All actions WITH Asynchronous call",function description(){
it('should return an action to get All Analysis by id', () => {
const store = mockStore({})
// Return the promise
return store.dispatch(actions.getAnalysisById('costnomics_Actual vs Budjected'))
.then(() => {
// const actionss = store.getActions()
console.log('store',store)
})
})
});
Running the above test case produces the following error,
TypeError: Cannot read property 'then' of undefined
When I pass plain objects as return value for the action creators instead of dispatcher, everything is working fine. But when mulitple dispatchers are returned from the action creator, it gives me the above error?. Why is this happening?
Your thunk function isn't actually returning a promise. You need to have something like return request at the end so that the promise is returned from the thunk, and from there returned from store.dispatch().
I created a simple thunk action to get data from an API. It looks like this:
import fetch from 'isomorphic-fetch';
function json(response) {
return response.json();
}
/**
* Fetches booksfrom the server
*/
export function getBooks() {
return function(dispatch) {
return fetch("http://localhost:1357/book", {mode: "cors"})
.then(json)
.then(function(data) {
dispatch({
type: "GET_Books",
books: data
});
// This lets us use promises if we want
return(data);
});
}
};
Then, I wrote a test like this:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {getBooks} from '../../actions/getBooks';
import nock from 'nock';
import fetch from 'isomorphic-fetch';
import sinon from 'sinon';
it('returns the found devices', () => {
var devices = nock("http://localhost:1357")
.get("/book")
.reply(200,
{});
const store = mockStore({devices: []});
var spy = sinon.spy(fetch);
return store.dispatch(getBooks()).then(() => {
}).catch((err) => {
}).then(() => {
// https://gist.github.com/jish/e9bcd75e391a2b21206b
expect(spy.callCount).toEqual(1);
spy.retore();
});
});
This test fails - the call count is 0, not 1. Why isn't sinon mocking the function, and what do I need to do to make it mock the function?
You are importing fetch in your test file and not calling it anywhere. That is why call count is zero.
This begs the question of why you are testing that the action creator is called in the first place when the test description is "returns the found devices".
The main purpose of thunk action creators is to be an action creator which returns a function that can be called at a later time. This function that is called at a later time can receive the stores dispatch and state as its arguments. This allows the returned function to dispatch additional actions asynchronously.
When you are testing a thunk action creator you should be focus on whether or not the correct actions are dispatched in the following cases.
The request is made
The response is received and the fetch is successful
An error occurs and the fetch failed
Try something like the following:
export function fetchBooksRequest () {
return {
type: 'FETCH_BOOKS_REQUEST'
}
}
export function fetchBooksSuccess (books) {
return {
type: 'FETCH_BOOKS_SUCCESS',
books: books
}
}
export function fetchBooksFailure (err) {
return {
type: 'FETCH_BOOKS_FAILURE',
err
}
}
/**
* Fetches books from the server
*/
export function getBooks() {
return function(dispatch) {
dispatch(fetchBooksRequest(data));
return fetch("http://localhost:1357/book", {mode: "cors"})
.then(json)
.then(function(data) {
dispatch(fetchBooksSuccess(data));
// This lets us use promises if we want
return(data);
}).catch(function(err) {
dispatch(fetchBooksFailure(err));
})
}
};
Tests.js
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import fetchMock from 'fetch-mock' // You can use any http mocking library
import {getBooks} from '../../actions/getBooks';
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('Test thunk action creator', () => {
it('expected actions should be dispatched on successful request', () => {
const store = mockStore({})
const expectedActions = [
'FETCH_BOOKS_REQUEST',
'FETCH_BOOKS_SUCCESS'
]
// Mock the fetch() global to always return the same value for GET
// requests to all URLs.
fetchMock.get('*', { response: 200 })
return store.dispatch(fetchBooks())
.then(() => {
const actualActions = store.getActions().map(action => action.type)
expect(actualActions).toEqual(expectedActions)
})
fetchMock.restore()
})
it('expected actions should be dispatched on failed request', () => {
const store = mockStore({})
const expectedActions = [
'FETCH_BOOKS_REQUEST',
'FETCH_BOOKS_FAILURE'
]
// Mock the fetch() global to always return the same value for GET
// requests to all URLs.
fetchMock.get('*', { response: 404 })
return store.dispatch(fetchBooks())
.then(() => {
const actualActions = store.getActions().map(action => action.type)
expect(actualActions).toEqual(expectedActions)
})
fetchMock.restore()
})
})