typescript 如何在React redux-toolkit测试中使用伪数据模拟appSelector

pjngdqdw  于 2023-04-13  发布在  TypeScript
关注(0)|答案(1)|浏览(106)

我有以下react函数组件:

const AssetsPage = () => {

    const [filteredAssets, setFilteredAssets] = useState<Asset[]>([]);
    const assetsState = useAppSelector(selectAssets);
    ...
    
    useEffect(() => {
        setFilteredAssets(
            assetsState.assets
        );
    }, [assetsState.assets])

    useEffect(() => {
        store.dispatch(fetchAssetsAsync(null));
    }, [])

    
    const columns: GridColDef[] = [
        ...some column definitions...
    ];

  

    return (
        <>
            <Typography variant="h2" ml={2}>
                {t("Asset.Title")}
            </Typography>
            <DataTable
              rows={filteredAssets}
              columns={columns}
              rowId="globalId"
            />
        </>
    )
}

export default AssetsPage

和相应的切片:

import {Asset} from "../../models/Asset";
import {createAsyncThunkWithErrorHandling} from "../../utils/thunkHelper";
import {createSlice} from "@reduxjs/toolkit";
import {RootState} from "../../app/store";
import {createAsset, deleteAsset, getAssets, updateAsset} from "../../api/assets";

const initialState = {
    isLoaded: false,
    assets: [] as Asset[],
}

...

export const fetchAssetsAsync = createAsyncThunkWithErrorHandling(
    "assets/fetch",
    async (payload: string) => {
        return await getAssets(payload);
    }
)
...

export const oeeAssetsSlice = createSlice({
    name: "assets",
    initialState,
    reducers: {},
    extraReducers: (builder) => {
        builder.addCase(fetchAssetsAsync.fulfilled, (state, action) => {
            state.isLoaded = true;
            state.assets = [
                ...action.payload,
            ];
        });
       ...
    }

})
export const selectAssets = (state: RootState) => state.assets;

export default oeeAssetsSlice.reducer;

这工作得很好,它从后端加载数据(或者,如果我将fetchAssetsAsync实现替换为静态数据,它将正确加载它)。
我正在尝试做一些单元测试。渲染空表工作正常:但是无论我怎么尝试,我都无法将数据注入到assetsState

import {fireEvent, render, screen, waitFor} from "@testing-library/react";
import {Provider} from "react-redux";
import {RootState, store} from "../../../app/store";
import AssetsPage from "./AssetsPage";
import {createTheme} from "@mui/material/styles";
import {ThemeOptions} from "../../ThemeOptions/ThemeOptions";
import {ThemeProvider} from "@mui/material";
import oeeAssetsReducer, {selectAssets} from "../../../features/assets/oeeAssetsSlice";
import {useAppSelector} from "../../../app/hooks";
import appReducer from "../../../features/app/appSlice";
import userReducer from "../../../features/user/userSlice";

describe("AssetListPage", () => {
    const theme = createTheme(ThemeOptions);

    // This test works fine.
    test("renders the empty component", async () => {
        render(
            <ThemeProvider theme={theme}>
                <Provider store={store}>
                    <AssetsPage/>
                </Provider>
            </ThemeProvider>
        );
        expect(screen.getByText("No rows")).toBeInTheDocument();
    });

    // todo: this doesnt work.
   const asset = {
        globalId: "asset-1",
        description: "test asset",
        businessUnitCode: "bu-1",
        localId: "123",
        enabledFlag: 1,
    };

    jest.mock("../../../app/hooks", () => ({
        useAppSelector: (selectAssets: any) => ({
            assetsState: {
                isLoaded: true,
                assets: [asset]
            }
        })
    }));

    jest.mock("react-i18next", () => ({
        useTranslation: () => ({
            t: (key: string) => key
        })
    }));

    test("renders the component with one data line", async () => {

       render(<ThemeProvider theme={theme}>
            <Provider store={store}>
                <AssetsPage/>
            </Provider>
        </ThemeProvider>
        );

        render(
            <ThemeProvider theme={theme}>
                <Provider store={store}>
                    <AssetsPage/>
                </Provider>
            </ThemeProvider>
        );

        expect(screen.getByText("Asset.GlobalAssetId")).toBeInTheDocument();
        expect(screen.getByText("Asset.Description")).toBeInTheDocument();
        expect(screen.getByText("Asset.BusinessUnit")).toBeInTheDocument();
        expect(screen.getByText("Asset.LocalId")).toBeInTheDocument();  // it renders the "empty table" instead
        expect(screen.getByText("Asset.Disable")).toBeInTheDocument();
        expect(screen.getByText("Asset.Actions")).toBeInTheDocument();
    })
  
});

测试总是呈现一个没有任何数据的表。我在这里错过了什么?我不能安装Enzyme,试着将其喂给chatGPT,但到目前为止没有任何效果。

ecfdbz9o

ecfdbz9o1#

以下是一个可能的解决方案:

test ("render the emtpy component with one data line", async () => {
        const mockUser = getAWhoamiSlice_SUPERADMIN()
        const mockShifts = getATestShiftsReducer_EMTPY()
        const mockBusinessUnits =getBusinessUnitsReducer_EMPTY()
        const mockAssets = getAssetsReducer_TWO_ITEMS()

        const mockStore = configureStore({
            middleware: undefined,
            reducer: {
                whoami: whoamiReducer,
                shifts: oeeShiftsReducer,
                businessUnits: oeeBusinessUnitReducer,
                assets: oeeAssetsReducer,
            },
            preloadedState: {
                whoami: mockUser,
                shifts: mockShifts,
                businessUnits: mockBusinessUnits,
                assets: mockAssets
            },
        });

        render(
            <ThemeProvider theme={theme}>
                <Provider store={mockStore}>
                    <AssetsPage/>
                </Provider>
            </ThemeProvider>
        );

        expect(screen.queryByText("No rows")).not.toBeInTheDocument();
        expect(screen.getByText("Asset.GlobalAssetId")).toBeInTheDocument();
        expect(screen.getByText("Asset.Description")).toBeInTheDocument();
        expect(screen.queryByText(mockAssets.assets[0].name)).toBeInTheDocument();
    })

一些看不见的功能的解释:

  • reducer与应用程序中使用的reducer相同。但是,我们没有使用它们,而是使用初始数据预加载测试的状态。
  • 预加载的状态函数返回类似的数据,否则这些数据将随着REST调用而到达。

相关问题