'Redux, React-testing-lib: How to change state in my tests?
I am following the test setup example from the Redux site but can't seem to get mock data to load for each test.The tests seem to be pulling the initial state from my slice when I run the tests, because when I change that state to match my expected results, it works. I need to be able to load mock data for each test. Not sure what I am doing wrong. Please be gentle, I'm new to redux. Thanks!
This is the test utility used in the test class, pretty much copied and pasted from the Redux site:
import React from "react";
import { render as rtlRender } from "@testing-library/react";
import { configureStore } from "@reduxjs/toolkit";
import { Provider } from "react-redux";
import reducer from "../../src/redux/rootReducer";
function renderTestComponent(
ui,
{
initialState,
store = configureStore({ reducer }, initialState),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>;
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
}
// re-export everything
export * from "@testing-library/react";
// override render method
export { renderTestComponent };
Here is the test.js where I have an initial state and expect to be able to change that state in my tests if I need to.The first test uses the initial state and the second test has the modified state.
import React from "react";
import CoinList from "../../components/coinlist/CoinList";
import { screen } from "@testing-library/react";
import { renderTestComponent } from "../testUtil";
describe("CoinList", () => {
const initialState = {
coinsAll: [],
status: "idle",
error: null,
};
const renderCoinList = (args, state) => {
const defaultProps = {};
const props = { ...defaultProps, ...args };
return renderTestComponent(<CoinList {...props} />, { state });
};
test("renders spinner", () => {
renderCoinList(null, initialState);
expect(screen.getByAltText(/Loading/i)).toBeInTheDocument();
});
test("renders coin list", () => {
renderCoinList(null, {
coinsAll: [
{ id: "ethereum", name: "Ethereum", current_price: "4500" },
{ id: "bitcoin", name: "BTC", current_price: "100,000" },
],
status: "succeeded",
error: null,
});
expect(screen.getByText(/Ethereum/i)).toBeInTheDocument();
expect(screen.getByText(/BTC/i)).toBeInTheDocument();
});
});
Here is the component under test:
import React from "react";
import { useSelector } from "react-redux";
import Spinner from "../spinner/Spinner";
import {
selectStatus,
selectError,
selectCoinsAll,
} from "../../../src/redux/slices/selectors";
const CoinList = () => {
const coinListSelector = useSelector(selectCoinsAll);
const coinStatusSelector = useSelector(selectStatus);
const errorMessageSelector = useSelector(selectError);
if (coinStatusSelector === "loading" || coinStatusSelector === "idle") {
return (
<div>
<Spinner />
</div>
);
} else if (coinStatusSelector === "succeeded") {
console.log("inside succeeded");
return (
<div>
{coinListSelector.map((coin) => (
<h4 key={coin.id}>
Coin: {coin.name}, Price: {coin.current_price}
</h4>
))}
</div>
);
} else if (coinStatusSelector === "failed") {
return (
<div>
<h1>{errorMessageSelector}</h1>
</div>
);
}
};
export default CoinList;
And here is the slice where if I modify the initial state here, that is what is loaded into the test state for some reason. I'm sure it's some dumb mistake.
import axios from "axios";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
const initialState = {
coinsAll: [],
status: "idle",
error: null,
};
export const fetchAllCoins = createAsyncThunk(
"coinsAll/fetchAllCoins",
async () => {
const res = await axios.get(
`https://api.coingecko.com/api/v3/coins/markets/?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false`
);
return res.data;
}
);
export const coinsAll = createSlice({
name: "coinsAll",
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchAllCoins.pending, (state) => {
state.status = "loading";
})
.addCase(fetchAllCoins.fulfilled, (state, action) => {
state.coinsAll = action.payload;
state.status = "succeeded";
})
.addCase(fetchAllCoins.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
export default coinsAll.reducer;
Solution 1:[1]
Try changing your test utility from:
return renderTestComponent(<CoinList {...props} />, { state });
to
return renderTestComponent(<CoinList {...props} />, { initialState: state });
Your render wrapper defines the start arg as initialState, so you have to use the same name in your test
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
