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

huangapple go评论66阅读模式
英文:

How to mock appSelector with dummy data in React redux-toolkit tests

问题

以下是您提供的代码的翻译部分:

我有以下的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[] = [
        ...某些列定义...
    ];

  

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

export default AssetsPage

和相应的切片(slice):

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;

这段代码用于创建React组件和相应的Redux切片。

请注意,这只是您代码的翻译部分。如果您需要更多的帮助或有其他问题,请随时提出。

英文:

I have the following react function component:


const AssetsPage = () =&gt; {

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

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

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

  

    return (
        &lt;&gt;
            &lt;Typography variant=&quot;h2&quot; ml={2}&gt;
                {t(&quot;Asset.Title&quot;)}
            &lt;/Typography&gt;
            &lt;DataTable
              rows={filteredAssets}
              columns={columns}
              rowId=&quot;globalId&quot;
            /&gt;
        &lt;/&gt;
    )
}

export default AssetsPage

And the corresponding slice:

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

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

...

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

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

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


export default oeeAssetsSlice.reducer;

This works just fine, it loads data from the backend (or, if I replace the fetchAssetsAsync implementation with static data, it loads it properly.)

I am trying to do some unit testing. Rendering the empty table works fine: but I cannot inject data to the assetsState no matter how I try.

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

describe(&quot;AssetListPage&quot;, () =&gt; {
    const theme = createTheme(ThemeOptions);

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


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

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


    jest.mock(&quot;react-i18next&quot;, () =&gt; ({
        useTranslation: () =&gt; ({
            t: (key: string) =&gt; key
        })
    }));

    test(&quot;renders the component with one data line&quot;, async () =&gt; {

       render(&lt;ThemeProvider theme={theme}&gt;
            &lt;Provider store={store}&gt;
                &lt;AssetsPage/&gt;
            &lt;/Provider&gt;
        &lt;/ThemeProvider&gt;
        );

        render(
            &lt;ThemeProvider theme={theme}&gt;
                &lt;Provider store={store}&gt;
                    &lt;AssetsPage/&gt;
                &lt;/Provider&gt;
            &lt;/ThemeProvider&gt;
        );

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

The test always renders a table without any data. What am I missing here? I cannot install Enzyme, tried feeding this to chatGPT, but nothing worked so far.

答案1

得分: 0

这是一个可能的解决方案:

test("呈现带有一条数据行的空组件", 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("没有行")).not.toBeInTheDocument();
    expect(screen.getByText("Asset.GlobalAssetId")).toBeInTheDocument();
    expect(screen.getByText("Asset.Description")).toBeInTheDocument();
    expect(screen.queryByText(mockAssets.assets[0].name)).toBeInTheDocument();
})

关于未显示函数的一些解释:

  • Reducers是应用程序中使用的相同reducers。然而,在测试中,我们使用初始数据预加载了状态,而不是实际使用reducers。
  • 预加载状态的函数返回的是类似于通过REST调用获取的数据。
英文:

Here is a possible solution:

    test (&quot;render the emtpy component with one data line&quot;, async () =&gt; {
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(
&lt;ThemeProvider theme={theme}&gt;
&lt;Provider store={mockStore}&gt;
&lt;AssetsPage/&gt;
&lt;/Provider&gt;
&lt;/ThemeProvider&gt;
);
expect(screen.queryByText(&quot;No rows&quot;)).not.toBeInTheDocument();
expect(screen.getByText(&quot;Asset.GlobalAssetId&quot;)).toBeInTheDocument();
expect(screen.getByText(&quot;Asset.Description&quot;)).toBeInTheDocument();
expect(screen.queryByText(mockAssets.assets[0].name)).toBeInTheDocument();
})

Some explanation for the unseen functions:

  • The reducers are the same reducers used in the application. They are not used however, we preload the state for the tests with initial data.
  • the preloaded state functions are returning similar data which would arrive otherwise with the REST calls.

huangapple
  • 本文由 发表于 2023年3月3日 20:44:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/75627242.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定