Jest mock redux toolkit actions/store files - reactjs

I'm using Redux toolkit in a React.js app, and I extracted my logic as below:
Store.tsx
const history = createHashHistory();
const middleware = [
...getDefaultMiddleware().concat(routerMiddleware(history)),
];
const Store = configureStore({
reducer: {
router: connectRouter(history) as Reducer<any, any>,
/* my reducers */
}, middleware
});
MySlice.tsx
import {createSlice} from '#reduxjs/toolkit';
const initialState = {
/* content of my state */
};
const mySlice = createSlice(
{
name: "myState",
initialState,
reducers: {
myAction: (state: MyState) => {
// Whatever here...
}
},
extraReducers: builder => {
/* My extra reducers */
}
});
export const MySlice = mySlice;
And then I have my function:
MySuperFunc.tsx
export const superFunc = () => {
/* content of my function */
const {myAction} = MySlice.actions;
Store.dispatch(myAction({my: 'payload'}));
};
I would like to unit test it with Jest. I want to mock the content of my Store/MySlice because the configureStore & createSlice doing extra logics and seems to require some configuration.
I'm a little lost from React.js best practices & documentation regarding mock, setMock and spyOn.
superFunc.spec.ts
const dispatchMockFn = jest.fn();
// Sol: 1
jest.mock('<< path >>/Store', () => ({dispatch: dispatchMockFn )});
// Sol: 2
jest.setMock('<< path >>/Store', {dispatch: dispatchMockFn});
// Sol: 3
jest.spyOn(Store, 'dispatch');
describe('superFunc', () => {
it('should call store', () => {
superFunc();
return expect(dispatchMockFn).toHaveBeenCalledWith(/* results of myAction({my: 'payload'}) */);
});
});
The problem I faced some error by code executed in the Store:
Seems to be normal because I used "Store" which is not exporting only an object, we have some extra code inside (createHistory etc.).
I searched a solution to mock entirely a module, that's why I try setMock/mock, it change a little bit the error but know it complaining regarding MySlice (extraReducers) saying that my promises.fulfilled/rejected are not defined etc.
But I don't want to go deep inside the Store/Slice config, I just want to mock the file content.
Any idea/recommendation?

I found the solution:
The jest.mock factory must be adapted from your export (export default VS. export const = {...})
Example 1:
MySuperFunc.tsx (with export default)
export default ({superFunc: () => 'hello world'});
Unit test:
import superFunc from './MySuperFunc';
jest.mock('./MySuperFunc', () => ({superFunc: jest.fn()});
it('should return toto', () => {
(MySuperFunc.superFunc as any).mockReturnValue('toto');
return expect(MySuperFunc.superFunc).toHaveBeenCalledWith('toto');
});
But, in the example 2 if you change your export by:
// MySuperFunc.tsx
export const MyVariable = {superFunc: () => 'hello world'});
Your jest.mock must be adapted as:
jest.mock('./MySuperFunc', () => ({MyVariable: {superFunc: jest.fn()}});
So when I re-take my question, I have 2 choices:
I can change my export in my slice file by using an export default
Or I adapt my jest.mock including {MySlice: {actions: {myAction: () => '...'}}}
My apologies, this topic is clearly not related to "redux toolkit" but more on how you deals with your export content from your typescript files.

Related

Writing a TypeScript Interface for React Context, how to describe the interface/ different types to be passed as values?

I am trying to write a React Context for my application
This is a follow up from my previous question:
How to pass value to Context Provider in TypeScript?
I would please like some help describing different value types to be passed through the provider.
Overview:
I am currently trying to construct a context provider that can be used across the scope of my application in TypeScript.
It contains some useState hooks and and an asynchronous function to be passed through as values the provider to all the child components.
ResultConextProvider.tsx
export const ResultContextProvider = () => {
const [isLoading, setIsLoading] = useState<boolean>(false)
const [greenStatus, setGreenStatus] =
useState(new Set<MyEnum>());
const [redStatus, setRedStatus] =
useState(new Set<MyEnum>());
const [searchTerm, setSearchTerm] = useState<string>('')
// objects to be passed values
const greenValue = {greenStatus, setGreenStatus};
const redValue = {redStatus, setRedStatus};
const searchValue = {searchTerm, setSearchTerm};
// api function coming from tested API spec (external)
const getResults = async () => {
setIsLoading(true)
myAPI.myGet(greenStatus, redStatus).then((result) => {
setResults(result.data);
})
setIsLoading(false)
}
return (
<ResultContext.Provider value={{getResults, greenValue, redValue, searchValue}}>
{children}
</ResultContext.Provider>
}
export const useResultContext = () => useContext(ResultContext);
As you can see above, I would like to pass the getResults function, my greenValues, redValus and searchValues to all my child components, the Provider implentation will look something like this:
index.tsx
import { ResultContextProvider } from "./contexts/ResultContextProvider";
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<ResultContextProvider
<Router>
<App />
</Router>
</ResultContextProvider>
);
When it comes to writing the interface, I am struggling to understand what needs to be represented prior to my component.
So far my interface looks something like this:
ResultConextProvider.tsx
interface ContextParametersType {
greenValue: { greenStatus: Set<MyEnum>, setGreenStatus:
Dispatch<SetStateAction<Set<MyEnum>>> };
redValue: { redStatus: Set<MyEnum>, setRedStatus:
Dispatch<SetStateAction<Set<MyEnum>>> };
///INCORRECT //
// searchValue: {searchTerm: <string>(''), setSearchTerm:
Dispatch<SetStateAction<string> };
///UNSURE
// getResults : {
// What to write as interface for getResults
// }
}
I have worked out what needs to be declared for my greenValue and redValue, based on the answer to my previous question however struggling to fill out the rest of the interface specification
The Search Term:
///INCORRECT //
// searchValue: {searchTerm: (''), setSearchTerm:
Dispatch<SetStateAction };
I have tried to follow the pattern of the Enum state, particularly in declaring the state as type string and rerferecning the Dispatch<SetStateAction as the same type to change the state however this produces error
getResults
This is an asynchronous function with an Axios function inside, to be truthful i do not know where to begin within this one.
My confusion on these two steps persists when creating the ResultContext
const ResultContext = createContext<ContextParametersType>({
greenValue: {
greenStatus: new Set<FirmStatusEnum>(), setGreenStatus: () => {
}
},
redValue: {
redStatus: new Set<FirmStatusEnum>(), setRedStatus: () => {
}
},
// searchValue: {
// searchTerm: <string>(''), setSearchTerm: () => {
// }
// },
getResults()
// Not Sure how to initalsie this?
});
Does this make sense? Am I understanding the issues here correctly?
I would really like some help in understanding how to configure the context interfaces for this.
It is essentially a typescript adaptation of this tutorial
Here:
Thanks, please let me know if I need to provide clarity

Best approach to make non component functions update redux state without passing store.dispatch() as a parameter

So I'm creating my first ReactJS/redux application and I need a little guidance.
I've created a generic apiFetch<T>(method, params) : Promise<T> function which lives in api/apiClient.ts. (Not a React component, but called indirectly from React components)
Basically every fetchEmployee/fetchSettings/fetchWhatever method in rpc/rpcMethods.ts calls this apiFetch<T>() function.
What I'd like to achieve is a statusbar in my app which shows how many concurrent api calls are active. I therefore created a redux rpcStatusSlice based on this redux example.
Can I make apiFetch<T>() update the slice without passing the UseAppDispatch() result as a parameter from my React components?
If I directly import the store in apiClient.ts and call the state modifying functions from rpcStatusSlice on it I get this error:
Uncaught ReferenceError: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization
at Module.default (bundle.js:1444:42)
at Module../src/store/store.ts (bundle.js:1957:67)
at Module.options.factory (bundle.js:90091:31)
at __webpack_require__ (bundle.js:89541:33)
at fn (bundle.js:89762:21)
at Module../src/api/apiClient.ts (bundle.js:206:70)
at Module.options.factory (bundle.js:90091:31)
at __webpack_require__ (bundle.js:89541:33)
at fn (bundle.js:89762:21)
at Module../src/api/rpcMethods.ts (bundle.js:288:68)
apiFetch.ts:
import { store } from "../store/store";
import { incrementByAmount } from "../store/features/rpcStatusSlice";
export function apiFetch<T>(method: string, params: any): Promise<T> {
store.dispatch(incrementByAmount(1));
return fetch(apiUrl, {
method: "POST",
cache: "no-cache",
mode: "cors",
redirect: "follow",
body: JSON.stringify(getApiRequest(method, params)),
})
.then(etc)
./store/features/rpcStatusSlice.ts
import { createSlice, PayloadAction } from '#reduxjs/toolkit';
import { RootState } from '../store';
export interface ActiveRequest{
requestType: string;
}
export interface RpcStatus {
activeRequestsCount: 0;
activeRequests: ActiveRequest[];
}
export interface RpcStatusState {
value: RpcStatus;
status: 'idle' | 'loading' | 'failed';
}
const initialState: RpcStatusState = {
value: {
activeRequestsCount: 0,
activeRequests: []
},
status: 'idle',
};
export const rpcStatusSlice = createSlice({
name: 'rpcstatus',
initialState,
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value.activeRequestsCount += 1;
},
decrement: (state) => {
state.value.activeRequestsCount -= 1;
},
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value.activeRequestsCount += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = rpcStatusSlice.actions;
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.rpcstatus.value)`
export const selectCount = (state: RootState) => state.rpcStatus.value.activeRequestsCount;
export default rpcStatusSlice.reducer;
./store/store.ts
import { configureStore, ThunkAction, Action } from '#reduxjs/toolkit';
import rpcStatusReducer from './features/rpcStatusSlice';
export const store = configureStore({
reducer: {
rpcStatus: rpcStatusReducer
},
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
Can I make apiFetch() update the slice without passing the UseAppDispatch() result as a parameter from my React components?
This sounds like you want apiFetch() to do two things:
Make an HTTP request.
Update redux state.
This violates the separation of concerns principle. Instead, call apiFetch() from a thunk that then updates redux state.
If your StatusBar component is the only consumer of the number of concurrent api calls, I'd consider not even using redux for this. This counter is not necessarily even global state, I'd see it as volatile, local component state for the StatusBar. Therefore, the solution I'm proposing relies only on React. It takes advantage of the pattern of keeping state in the module scope of the apiClient. At runtime, your apiClient module exists as a singleton with its own scope (many modules importing the apiClient will always use the same instance).
This abstracts the task of counting api calls away and lets the other parts of the codebase use apiFetch() without worrying about the counter. No need to adjust your existing thunks etc..
// apiClient.ts
let incrementCounter = (): void => null;
let decrementCounter = (): void => null;
export const onRequestStart = (callback: () => void) => {
incrementCounter = callback;
};
export const onRequestEnd = (callback: () => void) => {
decrementCounter = callback;
};
export const apiFetch = (method, params) => {
incrementCounter();
// do actual api call
return someKindOfPromise.finally(decrementCounter);
};
// StatusBar.tsx (component showing the number of concurrent api calls)
import { onRequestStart, onRequestEnd } from 'apiClient.ts';
const StatusBar = () => {
const [numConcurrentCalls, setNumConcurrentCalls] = useState(0);
useEffect(() => {
// Tell api client to always call the following function when a request starts.
onRequestStart(() => setNumConcurrentCalls(num => num + 1));
// Tell api client to always call the following function when a request ends.
onRequestEnd(() => setNumConcurrentCalls(num => num - 1));
}, [setNumConcurrentApiCalls]);
return (
<div>
<p>Concurrent api calls: {numConcurrentCalls}</p>
</div>
);
};

Cannot read properties of undefined (reading 'ids') , while using globalSelectors,simpleSelectors of redux toolkit in createEntityAdapter

I am Learning Redux Toolkit and using createEntityAdapter for a simple todo app.
the problem is that I am getting errors whenever I try to access the todos from globalselectors or/and simple selectors of the todoAdapter.
I've created a CodeSandBox for this
This is the todoReducer code where I am setting
const todosAdapter = createEntityAdapter({
selectId: (todo) => todo._id,
});
const initialState = {
fetching: true,
error: null,
addingNew: false,
};
const todosSlice = createSlice({
name: 'todos',
initialState: todosAdapter.getInitialState(initialState),
reducers: {
pushNewTodo: todosAdapter.addOne,
addManyTodos: todosAdapter.addMany,
removeTodo: todosAdapter.removeOne,
editTodo: todosAdapter.updateOne,
},
extraReducers: { ... }
})
//* Errror in this line ,
export const globalTodosReducers = todosAdapter.getSelectors((st) => st.todos);
export const simpleTodosReducers = todosAdapter.getSelectors();
export const {
addTodo,
removeTodo,
toggleTodo,
editTodo,
clearTodos
} = todosSlice.actions;
export default todosSlice.reducer;
And when I want to use simpleSelectors , I get error in that also
// TodoApp.js
function TodoApp() {
.
.
.
// * Error in these lines
// const todos = globalTodosReducers.selectAll();
const todos = simpleTodosReducers.selectAll();
.
.
.
error is
Cannot read properties of undefined (reading 'ids')
The object returned by getSelectors contains many selector functions. Each one requires you to pass in the state object that contains the data you want to select from. You had the right idea here:
todosAdapter.getSelectors((st) => st.todos)
This tells the entity adapter to access the todos property of whatever object is passed in to the selector functions in order to get the data managed by the adapter.
When you call a selector, pass in your application's root state:
const todos = globalTodosReducers.selectAll(store.getState());
In the context of React, react-redux's useSelector function will do this for you:
const todos = useSelector(globalTodosReducers.selectAll);

how to get and send data using reactjs and redux

I have problem when trying to send data through the function action in redux,
my code is below
import React from 'react'
import {connect} from 'react-redux'
import {RetrieveCompany} from '../../folder/action/my.actions'
interface Icampaing{
campaing: my_data
}
// campaing IS WORKING WELL, GET ME ALL MY DATA
const Personal: React.FC<Icampaing> = ({campaing}, props: nay) => {
React.useEffect(()=>{
let pf_id: any = campaing.profile ? campaing.profile.id : 0
let pc_id: any = campaing.profile_ca
// THE PROBLEM IS HERE SHOW ME THE ERROR
// TypeError: props.RetrieveCompany is not a function
props.RetrieveCompany(pf_id, pc_id)
},[campaing])
return(<>
{campaing.all_data} // HERE WHEN LOAD DATA campaing WORKING WELL
</>)
}
const mapStateToProps = (state: any) =>({
campaing: state.campaing
})
const mapActionToProps = {
RetrieveCompany
}
export default connect(mapStateToProps, mapActionToProps)(Personal)
please help me, I think forget something.
best words, and happy new year.....!
You should use mapDispatchToProps instead of mapActionToProps
const mapDispatchToProps = (dispatch) => ({
RetrieveCompany: () => dispatch(RetrieveCompany())
// Important: this could be just
// RetrieveCompany depends on how you define your action.
// For naming, I would use camelCase
});
Because what you need to do here is to dispatch an action so that the store will update its state. Then you would read the data returned by mapStateToProps.
I think RetrieveCompany is not among props in deed. Try to spread the rest of the props if you do not want to explicitly name it:
interface Icampaing {
campaing: my_data
[propName: string]: any
}
const Personal: React.FC<Icampaing> = ({ campaing, ...props }) => {
...
or simply add it explicitly since you use it in the component anyways:
interface Icampaing {
campaing: my_data
RetrieveCompany: (pf_id: number, pc_id: number) => void
}
const Personal: React.FC<Icampaing> = ({ campaing, RetrieveCompany }) => {

How to spy on a default exported function with Jest?

Suppose I have a simple file exporting a default function:
// UniqueIdGenerator.js
const uniqueIdGenerator = () => Math.random().toString(36).substring(2, 8);
export default uniqueIdGenerator;
Which I would use like this:
import uniqueIdGenerator from './UniqueIdGenerator';
// ...
uniqueIdGenerator();
I want to assert in my test that this method was called while keeping the original functionality. I'd do that with jest.spyOn however, it requires an object as well as a function name as parameters. How can you do this in a clean way? There's a similar GitHub issue for jasmine for anyone interested.
I ended up ditching the default export:
// UniqueIdGenerator.js
export const uniqueIdGenerator = () => Math.random().toString(36).substring(2, 8);
And then I could use and spy it like this:
import * as UniqueIdGenerator from './UniqueIdGenerator';
// ...
const spy = jest.spyOn(UniqueIdGenerator, 'uniqueIdGenerator');
Some recommend wrapping them in a const object, and exporting that. I suppose you can also use a class for wrapping.
However, if you can't modify the class there's still a (not-so-nice) solution:
import * as UniqueIdGenerator from './UniqueIdGenerator';
// ...
const spy = jest.spyOn(UniqueIdGenerator, 'default');
one could also mock the import and pass the original implementation as mock implementation, like:
import uniqueIdGenerator from './UniqueIdGenerator'; // this import is a mock already
jest.mock('./UniqueIdGenerator.js', () => {
const original = jest. requireActual('./UniqueIdGenerator')
return {
__esModule: true,
default: jest.fn(original.default)
}
})
test(() => {
expect(uniqueIdGenerator).toHaveBeenCalled()
})
Here is a way of doing it for a default export without modifying the import (or even needing an import in the test at all):
const actual = jest.requireActual("./UniqueIdGenerator");
const spy = jest.spyOn(actual, "default");
In some cases you have to mock the import to be able to spy the default export:
import * as fetch from 'node-fetch'
jest.mock('node-fetch', () => ({
default: jest.fn(),
}))
jest.spyOn(fetch, 'default')
Mock only the default export, or any other export, but keep remaining exports in module as original:
import myDefault, { myFunc, notMocked } from "./myModule";
jest.mock("./myModule", () => {
const original = jest.requireActual("./myModule");
return {
__esModule: true,
...original,
default: jest.fn(),
myFunc: jest.fn()
}
});
describe('my description', () => {
it('my test', () => {
myFunc();
myDefault();
expect(myFunct).toHaveBeenCalled();
expect(myDefault).toHaveBeenCalled();
myDefault.mockImplementation(() => 5);
expect(myDefault()).toBe(5);
expect(notMocked()).toBe("i'm not mocked!");
})
});
Use 'default' as the second argument in spyOn function.
import * as MyHelperMethod from '../myHelperMethod';
jest.spyOn(MyHelperMethod, 'default');
What worked for me was a combination of the answer from Janne Annala and OP's own solution. All I wanted to test was that the helper method was called with the correct parameters as I had already written a test for the helper method and it didn't have any bearing on my subsequent test:
// myHelperMethod.js
export const myHelperMethod = (param1, param2) => { // do something with the params };
// someOtherFileUsingMyHelperMethod.js
import * as MyHelperMethod from '../myHelperMethod';
jest.mock('../myHelperMethod', () => ({
myHelperMethod: jest.fn(),
}));
let myHelperMethodSpy = jest.spyOn(MyHelperMethod, 'myHelperMethod');
// ...
// some setup
// ...
test(() => {
expect(myHelperMethodSpy).toHaveBeenCalledWith(param1, param2);
});
Here it is even simpler.
Mock your exported module 'addDelay' (has the sleep function in it) using jest.
const { sleep } = require('../../src/utils/addDelay');
jest.mock('../../src/utils/addDelay', () => {
const delay = jest.requireActual('../../src/utils/addDelay');
return {
...delay,
sleep: jest.fn(),
};});
And the test is as follows and check if sleep function was called with 1 sec as in arg.
test("Should delay 1 second if Okta user has no IDxM Roles", async () => {
// GIVEN
const MockSleep = sleep;
// WHEN
await getUser(req, res);
// THEN
expect(MockSleep).toHaveBeenCalledWith(1000);// sleep(1000): 1sec
});
I know I'm late to the party but I recently had this problem and wanted to share my solution as well ... though it seems a bit more unconventional but could be tweaked by someone with better knowledge.
I happen to have a file with the function that I would like to spy on.
// /foo/ModuleToBeMocked.ts
const fnToSpyOn = () => ...;
export default { fnToSpyOn }
This is then imported into a parent file that would bring, and export, alike functions. Sort of like a classification.
// /parent.ts
import fnToSpyOn from './foo/ModuleToBeMocked';
import someOtherFn from './foo/SomeOtherModule';
...
export { fnToSpyOn, someOtherFn, ... };
And this is how I test the fnToSpyOn
// /foo/ModuleToBeMocked.test.ts
import { ModuleToBeMocked } from '../parent';
const fnToSpyOnSpu = jest.spyOn(ModuleToBeMocked, 'fnToSpyOn');

Resources