I am working on unit test for my reducer and I don't know what I am doing wrong here. The error I am getting in the console is that Matcher error: received value must be a non-null object and received value is undefined.
Reducer
import uuid from "uuid/v4";
import {
ADD_TODO,
REMOVE_TODO,
TOGGLE_TODO,
EDIT_TODO
} from "../constants/Actions";
const reducer = (state, action) => {
switch (action.type) {
case ADD_TODO:
return [...state, { id: uuid(), task: action.task, completed: false }];
case REMOVE_TODO:
return state.filter(todo => todo.id !== action.id);
case TOGGLE_TODO:
return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
case EDIT_TODO:
return state.map(todo =>
todo.id === action.id ? { ...todo, task: action.task } : todo
)
default:
return state;
}
};
export default reducer;
And my test is.
import { useReducer } from "react";
import { act, renderHook } from "#testing-library/react-hooks";
import { reducer } from "../src/reducers/Todo";
import { ADD_TODO } from '../src/constants/Actions'
describe("Todo reducer tests", () => {
it("Add Todo", () => {
const { result } = renderHook(() => useReducer(reducer, []));
const [state, dispatch] = result.current;
act(() => {
dispatch({ type: ADD_TODO })
});
expect(state).toMatchObject({
id: 1,
task: 'Hello',
completed: true,
});
});
});
I recommend you to check redux offical how to write test correctly
this help me a lot and they explain for every stuff you need
here is my code where I use like redux show.
import reducer from './reducer';
import * as actionTypes from '../Actions/actionTypes';
describe('reducer CheclUp', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual({
favorite: [],
SearchPlaces: [],
});
});
it('should remove favorite and move it to SearchPlaces', () => {
expect(reducer({
favorite: [{Key: "1"}],
SearchPlaces: [],
}, {
type: actionTypes.REMOVE_FAVORITE,
payload: "1"
})).toEqual({
favorite: [],
SearchPlaces: [{Key: "1"}],
});
});
it('should remove SearchPlaces and move it to favorite', () => {
expect(reducer({
favorite: [],
SearchPlaces: [{Key: "1"}],
}, {
type: actionTypes.ADD_EXISTED_TO_FAV,
payload: "1"
})).toEqual({
favorite: [{Key: "1"}],
SearchPlaces: [],
});
});
it('should remove from SearchPlaces', () => {
expect(reducer({
favorite: [],
SearchPlaces: [{Key: "1"}],
}, {
type: actionTypes.REMOVE_SEARCHED_PLACE,
payload: "1"
})).toEqual({
favorite: [],
SearchPlaces: [],
});
});
it('should add to SearchPlaces', () => {
expect(reducer({
favorite: [],
SearchPlaces: [],
}, {
type: actionTypes.ADD_SEARCHED_PLACE,
payload: [{Key: "1"}]
})).toEqual({
favorite: [],
SearchPlaces: [{Key: "1"}],
});
});
it('should remove from SearchPlaces', () => {
expect(reducer({
favorite: [],
SearchPlaces: [],
}, {
type: actionTypes.ADD_FAVORITE,
payload: [{Key: "1"}]
})).toEqual({
favorite: [{Key: "1"}],
SearchPlaces: [],
});
});
});
Your state starts off as undefined because you don't have a default state set. Your reducer hits the default case and returns state, which isn't set in your first reducer run. I recommend setting a default state in your reducer:
const reducer = (state = [], action) => {
You can also pass in an initial state instead:
const { result } = renderHook(() => useReducer(reducer, []));
Related
This is a Redux issue.
Take a look at the logic that dispatched this action: {type: 'users/handleverification/fulfilled', payload: {…}, meta: {…}}
export const UserValidation = createAsyncThunk( //This Function should move to another folder
'users/handleverification',
async(thunkAPI) => {
try{
//Logic
if(Authenticated.status === 201){
localStorage.setItem('token', Authenticated.data.token);
delete Authenticated.token;
return Authenticated
console.log("Test me",Authenticated.data);
}
}
catch(error){
window.alert("error");
if(NewUser){
//delete User????????
}
}
}
)
Here is the Create Slice function:
export const userSlice = createSlice({
name: 'user',
initialState: {
UserData: {},
isFetching: false,
isSuccess: false,
isError: false,
errorMessage: '',
},
reducers: {
clearState: (state) => {
state.isError = false;
state.isSuccess = false;
state.isFetching = false;
return state;
},
},
extraReducers: {
[UserValidation.fulfilled]:(state,{payload}) => {
// console.log("Fullfilled:")
},
[UserValidation.pending]:(state) => {
// console.log("Pending");
},
[UserValidation.rejected]:(state,{payload}) => {
// console.log("Rejected");
},
}
})
Why do I get the error:
A non-serializable value was detected in an action, in the path: payload.config.adapter. Take a Look at the action 'users/handleverification'
I have started to create my application using #reduxjs/toolkit and kind of got stuck. I find no resource anywhere which can guide me with how to unit tests the logic in extraReducers. Any help would be appreciable.
Example:
Example:
const fetchList = createAsyncThunk('example/fetchList', async ({skip, reset, ...rest}) => {
const request = {
skip: reset ? initialState.skip : skip,
...rest,
};
return await getList(request);
});
const exampleSlice = createSlice({
name: 'example',
initialState: {id: '', list: []},
reducers: {
resetParams() {
return {id: '', list: []}
},
setId(state, {payload}) {
state.id = payload.id
}
},
extraReducers: {
[fetchList.pending]: (state) => {
state.fetching = true;
},
[fetchList.fulfilled]: (state, {payload = []}) => {
return {
fetching: false,
id: state.id + 1,
list: payload
}
},
[fetchList.rejected]: (state, {error}) => {
state.fetching = false;
},
},
});
//Tests .. for setId()
const initialState = {
id: 1,
list : []
}
const result = exampleSlice.reducer(initialState, exampleSlice.actions.setId({id: 10}))
expect(result.id).toEqual(10)
How can I test logic in extraReducers for fetchList.fulfilled and fetchList.rejected!
You can test it the same way as what you've shown.
Here's a simple way to just test the logic of the reducer based on the action types that createAsyncThunk outputs.
import reducer, {
fetchList
} from './exampleSlice';
describe('exampleSlice', () => {
describe('reducers', () => {
const initialState = { id: '', list: [], fetching: false }
it('sets fetching true when fetchList is pending', () => {
const action = { type: fetchList.pending.type };
const state = reducer(initialState, action);
expect(state).toEqual({ id: '', list: [], fetching: true });
});
it('sets the id and list when fetchList is fulfilled', () => {
const action = { type: fetchList.fulfilled.type, payload: { id: 1, list: [2, 3]} };
const state = reducer(initialState, action);
expect(state).toEqual({ id: 1, list: [2, 3], fetching: false });
});
it('sets fetching false when fetchList is rejected', () => {
const action = { type: fetchList.rejected.type, payload: { error: 'some error' } };
const state = reducer(initialState, action);
expect(state).toEqual({ id: '', list: [], fetching: false });
});
});
});
I also threw up a simple example of the same concept on a demo CSB: https://codesandbox.io/s/rtk-14-addmatcher-counter-test-l11mt?file=/src/features/counter/counterSlice.test.ts
I am using react and redux to write a todolist app.
Here is my initial state:
export const initialState = {
todoList:[
{
taskId:1
task: 'gym'
completed: true
},
{
taskId:2
task: 'buy dinner'
completed: false
}
]
}
const todoReducer = (state=initialState,action) => {
switch(action.type){
case UPDATE_TODO:
return Object.assign({}, state, {
todoList: state.todoList.map(todo =>
{
return todo.taskId === action.payload.taskId ?
Object.assign({},todo, {
task: action.payload.task,
completed: action.payload.completed
}) : todo
}
)
})
default:
return state;
}
}
export default todoReducer;
If I want to update the second task to 'buy book' and change completed to true, how can I add code in my reducer? The code above is not working now. Not sure the reason. Could anyone help?
Try this:
reducer
const initialState = {
todoList: [
{
taskId: 1,
task: "gym",
completed: true
},
{
taskId: 2,
task: "buy dinner",
completed: false
}
]
};
const todoReducer = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_TODO':
const { payload } = action;
return {
...state,
todoList: state.todoList.map(item => {
if (item.taskId === payload.taskId) {
return {
...item,
task: payload.task,
completed: payload.completed
};
} else {
return item;
}
})
};
default:
return state;
}
};
Unit test:
const output = todoReducer(initialState, {type: 'UPDATE_TODO', payload: {
taskId: 1,
task: "newTask",
completed: true
}});
console.log('output', output);
test output:
output { todoList:
[ { taskId: 1, task: 'newTask', completed: true },
{ taskId: 2, task: 'buy dinner', completed: false } ] }
I have problem with destructuring Typescript data object from my own Hook created in React.
export interface InitialState {
pokemonListLoading: false;
pokemonListLoadingFailed: false;
data: [];
}
interface FetchPokemonList {
type: typeof FETCH_POKEMON_LIST;
}
interface FetchPokemonListSuccess {
type: typeof FETCH_POKEMON_LIST_SUCCESS;
payload: PokemonList;
}
...
export type PokemonListActionTypes = FetchPokemonList | FetchPokemonListSuccess | FetchPokemonListError;
const dataFetchReducer = (state: InitialState, action: PokemonListActionTypes) => {
switch (action.type) {
case FETCH_POKEMON_LIST:
return {
...state,
pokemonListLoading: true,
pokemonListLoadingFailed: false,
};
case FETCH_POKEMON_LIST_SUCCESS:
return {
...state,
pokemonListLoading: false,
pokemonListLoadingFailed: false,
data: action.payload,
};
case FETCH_POKEMON_LIST_ERROR:
return {
...state,
pokemonListLoading: false,
pokemonListLoadingFailed: true,
};
default:
throw new Error();
}
};
export const fetchPokemonList = (initialUrl: string, initialData: []) => {
const [url, setUrl] = useState(initialUrl);
const [state, dispatch] = useReducer(dataFetchReducer, {
pokemonListLoading: false,
pokemonListLoadingFailed: false,
data: initialData,
});
useEffect(() => {
const fetchData = async () => {
dispatch({ type: FETCH_POKEMON_LIST });
try {
const result = await axios(url);
dispatch({ type: FETCH_POKEMON_LIST_SUCCESS, payload: result.data });
} catch (error) {
dispatch({ type: FETCH_POKEMON_LIST_ERROR });
}
};
fetchData();
}, [url]);
return [state, setUrl];
};
and whole component
import React, { FunctionComponent } from 'react';
import { fetchPokemonList, InitialState } from '../../hooks/fetchPokemonList';
const PokemonList: FunctionComponent = () => {
const [{
data: { results: pokemonList },
pokemonListLoading,
pokemonListLoadingFailed,
},
] = fetchPokemonList('https://pokeapi.co/api/v2/pokemon',[]);
return (
<div>
PokemonList
{pokemonListLoading ? (
<div>Laoding...</div>
) : (
pokemonList && pokemonList.map((pokemon: { name: string}) => (
<div key={pokemon.name}>{pokemon.name}</div>
))
)}
{pokemonListLoadingFailed && <div>Error</div>}
</div>
)
}
export { PokemonList }
error code displayed by Webstorm
TS2339: Property 'data' does not exist on type '{ pokemonListLoading:
boolean; pokemonListLoadingFailed: boolean; data: []; } | {
pokemonListLoading: boolean; pokemonListLoadingFailed: boolean; data:
PokemonList; } | Dispatch ...>>'.
The issue is within this line:
dispatch({ type: FETCH_POKEMON_LIST_SUCCESS, payload: result.data });
Where you send as payload without using a key for your new data value.
Then in the code section you're setting data with the payload object, which results in the error you're experiencing:
case FETCH_POKEMON_LIST_SUCCESS:
return {
...state,
pokemonListLoading: false,
pokemonListLoadingFailed: false,
data: action.payload,
};
Try passing your payload like this: payload: { data: result.data }.
Then set your data respectively: data: action.payload.data
In my component I want to check when the parameter has changed and update accordingly. However when I do this, I am seeing weird behaviour and multiple requests been made to my api.
my component:
componentWillMount() {
this.state = {
path: this.props.match.params.categoryName,
};
}
componentDidUpdate(prevProps) {
if (prevProps === undefined) {
return false;
}
if (this.state.path !== this.props.match.params.categoryName) {
this.getCategory()
}
}
getCategory() {
if (this.props.allPosts && this.props.allPosts.length) {
const categoryId = _.result(_.find(this.props.allPosts, v => (
v.name === this.props.match.params.categoryName ? v.id : null
)), 'id');
this.props.dispatch(Actions.fetchCategory(categoryId));
}
}
my action:
import Request from 'superagent';
import Constants from '../constants';
const Actions = {
fetchCategory: categoryId => (dispatch) => {
dispatch({ type: Constants.CATEGORY_FETCHING });
Request.get(`/api/v1/categories/${categoryId}`)
.then((data) => {
dispatch({
type: Constants.CATEGORY_RECEIVED,
category: { id: data.body.id, name: data.body.name },
category_posts: data.body.posts,
});
});
},
};
export default Actions;
my reducer:
import Constants from '../constants';
const initialState = {
posts: [],
category: [],
fetching: true,
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case Constants.CATEGORY_FETCHING:
return Object.assign({}, state, { fetching: true });
case Constants.CATEGORY_RECEIVED:
return Object.assign({}, state, { category: action.category,
posts: action.category_posts,
fetching: false });
default:
return state;
}
}