反応とReduxによるテスト駆動開発:サンク、スライスと要求モッキング


これらの記事の詳細を読む場合は、躊躇しない私の購読するnewsletter .😁
Reduxで書くテストは間違いなく直感的に聞こえるかもしれません.あなたがReduxで働いているならば、それはさらに複雑に思えるかもしれません.🥶
しかし、機能を追加する前にテストを書く前に、デザインパターン、アーキテクチャ、および使用する変数の名前について前向きに考えるので、より良いコードを書くことができます.🚀

プロジェクト


我々は、ユーザー管理ダッシュボードを構築している.基本的には、reduxを使用して考えると、我々はcrudアクションを実行したい.
次に、ユーザは以下のようになります.
  • ユーザを作成します.
  • ユーザを更新します.
  • ユーザを削除します.
  • ユーザーまたはユーザーのリストを取得します.
  • この小さなプロジェクトのユーザは4つの属性を持つでしょう.
  • ID
  • 名前
  • ユーザ名
  • メール
  • シンプルさのために、UIコードを書きません.我々は主にテスト環境を作成し、テストを書いて、スライスとthunkを我々が望むものを持っていることを確認することに集中します.

    プロジェクトのセットアップ


    まず第一に、シンプルな反応プロジェクトを作成します.
    yarn create react-app react-redux-test-driven-development
    
    プロジェクトが作成されると、プロジェクトの実行によってすべてが動作することを確認します.
    cd react-redux-test-driven-development
    yarn start
    
    そして、あなたは何か似ているhttp://localhost:3000 .

    次に、reduxパッケージをインストールしたいと思います.模擬アダプターは、サーバー上のリクエストをシミュレートするのに役立ちます.
    # Yarn
    yarn add @reduxjs/toolkit axios-mock-adapter axios
    
    すごい!インストールされたら、まずテストのために模擬データを書きましょう.🍔

    テストのためのデータのモッキング


    srcディレクトリで新しいディレクトリを作成しますutils . 次に、tests.data.js .
    このファイルは以下のメソッドと変数を含みます:
  • mocknetworkresponse :デフォルトのインスタンスでmockアダプタを作成し、必要なエンドポイントに対して任意のGETまたはPOSTリクエストをモックする
  • GetCreateUserResponse : POSTリクエストの応答を返す/user/
  • getUserListResponse : GETリクエストの応答を返す/user/
  • これらの方法を書きましょう.
    import axios from "axios";
    import MockAdapter from "axios-mock-adapter";
    
    const getCreateUserResponse = {
      id: 3,
      name: "Clementine Bauch",
      username: "Samantha",
      email: "[email protected]"
    };
    
    const getUserListResponse = [
      {
        id: 1,
        name: "Leanne Graham",
        username: "Bret",
        email: "[email protected]"
      },
      {
        id: 2,
        name: "Ervin Howell",
        username: "Antonette",
        email: "[email protected]"
      },
    ];
    
    // Adding mock network response that is used in tests
    
    const mockNetWorkResponse = () => {
      const mock = new MockAdapter(axios);
    
      mock.onGet(`/users/`).reply(200, getUserListResponse);
      mock.onPost(`/users/`).reply(200, getCreateUserResponse);
    };
    
    export {
      mockNetWorkResponse,
      getCreateUserResponse,
      getUserListResponse,
    };
    
    すごい!モックアダプターで準備ができて、我々は店の初期化とスライスのテストを書くことに集中することができます.

    書き込みテスト


    これは最も興味深い部分です.レッツゴーTDD .🔥
    まず、ストアを作成して設定しましょう.srcディレクトリで新しいディレクトリを作成しますindex.js . このファイルで、ストアを初期化します.
    import { configureStore } from "@reduxjs/toolkit";
    import { combineReducers } from "redux";
    
    const rootReducer = combineReducers({
      // Adding the reducers
    });
    
    export const store = configureStore({
      reducer: rootReducer,
    });
    

    ユーザスライスの作成


    “スライス”は、あなたのアプリケーションの単一の機能のためのReduxリダラー論理とアクションのコレクションです.The userSlice は、アクションとリダーズを持っている.
    スライスのデフォルト状態は空の配列でなければなりませんusers .
    テストを書いて失敗しましょう.新しいディレクトリを作成するsrc/store 呼ばれるslices .
    このディレクトリの中にuser.test.js . このファイルは、私たちが書くテストを含みますuserSlice .
    最初のテストは、ストアが空であるか未定義であることを確認することです.初期状態はおそらくこのようになります.
    const initialState = {
      users: [],
      loading: false,
      error: null
    };
    
    最初のテストを書きましょう.

    初期状態のテスト


    user.test.js 次のテストを追加します
    import reducer, {
        initialState,
      } from "./user";
      /**
       * Testing the initial state
       */
    
      test("Should return initial state", () => {
        expect(
          reducer(undefined, {
            type: undefined,
          })
        ).toEqual(initialState);
      });
    
    では、yarn test コマンド.テストは失敗するでしょう.❌
    全く普通.我々は定義していないuserSlice , 還元器と初期状態
    スライスディレクトリ内で、ユーザーと呼ばれるファイルを作成します.js
    export const initialState = {
      users: [],
      loading: false,
      error: null
    };
    
    export const userSlice = createSlice({
      name: "users",
      initialState: initialState,
      extraReducers: () => {
      },
    });
    
    export default userSlice.reducer;
    
    また、中にストアのスライス減速剤を登録するstore/index.js .
    import { configureStore } from "@reduxjs/toolkit";
    import { combineReducers } from "redux";
    import { userSlice } from "./slices/user";
    
    const rootReducer = combineReducers({
      users: userSlice.reducer,
    });
    
    export const store = configureStore({
      reducer: rootReducer,
    });
    
    テストを再度実行します.✅

    ユーザの作成テスト


    このために、我々はthunkを書く必要があります.thunkは、ストアのディスパッチメソッドを引数として受け取り、後にAPIまたは副作用が終了した後に同期アクションを送信するために使用する関数です.
    まず、この機能のテストを書きましょう.
    import reducer, {
        initialState,
        addUser
      } from "./user";
      import {
        mockNetWorkResponse,
        getCreateUserResponse,
      } from "../../utils/tests.data";
    
     /**
       * Testing the createUser thunk
       */
    
      describe("Create a new user", () => {
        beforeAll(() => {
          mockNetWorkResponse();
        });
    
        it("Should be able to create a new user", async () => {
          // Saving previous state
          const previousState = store.getState().users;
    
          const previousUsers = [...previousState.users];
          previousUsers.push(getCreateUserResponse);
    
          // Dispatching the action
    
          const result = await store.dispatch(addUser(getCreateUserResponse));
    
          const user = result.payload;
    
          expect(result.type).toBe("users/addUser/fulfilled");
          expect(user).toEqual(getCreateUserResponse);
    
          const state = store.getState().users;
    
          expect(state.users).toEqual(previousUsers);
        });
    
    このテストでは、
  • 前の状態を保存し、users 更新を行う前に期待される状態にする.これは次の状態を比較するときに役立ちます.
  • アクションを派遣し、それが満たされることを確認し、期待される状態と実際の状態を比較する.
  • 繰り返しますが、テストは失敗します.作成したユーザー機能のために、サンクと還元器を加えましょう.
    import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
    import axios from "axios";
    
    const addUser = createAsyncThunk("users/addUser", async (user) => {
      const res = await axios.post(`/users/`, user);
      return res.data;
    });
    
    export const initialState = {
      users: [],
      loading: false,
      error: null
    };
    
    export const userSlice = createSlice({
      name: "users",
      initialState: initialState,
      extraReducers: () => {
        /*
         * addUser Cases
         */
    
        builder.addCase(addUser.pending, (state) => {
          state.loading = true;
        });
        builder.addCase(addUser.rejected, (state, action) => {
          state.loading = false;
          state.error = action.error.message || "Something went wrong";
        });
        builder.addCase(addUser.fulfilled, (state, action) => {
          state.loading = true;
          state.users.push(action.payload);
        });
      },
    });
    
    export default userSlice.reducer;
    export { addUser };
    
    テストを再度実行してください.✅

    ユーザのリストを取得するためのテストを書く


    まず、この機能のテストを書きましょう.
    import reducer, {
        initialState,
        addUser,
        fetchUsers
      } from "./user";
      import {
        mockNetWorkResponse,
        getCreateUserResponse,
        getUserListResponse
      } from "../../utils/tests.data";
    
    ...
      /**
       * Testing the fetchUsers thunk
       */
    
      describe("List all users", () => {
        beforeAll(() => {
          mockNetWorkResponse();
        });
    
        it("Shoudl be able to fetch the user list", async () => {
          const result = await store.dispatch(fetchUsers());
    
          const users = result.payload;
    
          expect(result.type).toBe("users/fetchUsers/fulfilled");
          expect(users).toEqual(getUserListResponse);
    
          const state = store.getState().users;
    
          expect(state.users).toEqual(getUserListResponse);
        });
      });
    
    そして、テストが失敗することを確認してください.
    減速機とタンクを加えましょう.
    import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
    import axios from "axios";
    
    const fetchUsers = createAsyncThunk(
      "users/fetchUsers",
      async () => {
        const response = await axios.get(`/users/`);
        return response.data;
      }
    );
    
    const addUser = createAsyncThunk("users/addUser", async (user) => {
      const res = await axios.post(`/users/`, user);
      return res.data;
    });
    
    export const initialState = {
      users: [],
      loading: false,
      error: null
    };
    
    export const userSlice = createSlice({
      name: "users",
      initialState: initialState,
      extraReducers: () => {
        /*
         * addUser Cases
         */
    
        builder.addCase(addUser.pending, (state) => {
          state.loading = true;
        });
        builder.addCase(addUser.rejected, (state, action) => {
          state.loading = false;
          state.error = action.error.message || "Something went wrong";
        });
        builder.addCase(addUser.fulfilled, (state, action) => {
          state.loading = true;
          state.users.push(action.payload);
        });
    
        /*
         * fetchUsers Cases
         */
    
        builder.addCase(fetchUsers.pending, (state) => {
          state.loading = true;
        });
        builder.addCase(fetchUsers.fulfilled, (state, action) => {
          state.loading = false;
          state.users = action.payload;
        });
        builder.addCase(fetchUsers.rejected, (state) => {
          state.loading = false;
        });
      },
    });
    
    export default userSlice.reducer;
    export { addUser, fetchUsers };
    
    そして、テストは通過しなければなりません.✅
    すごい!我々はちょうどredux、thunk、およびaxios mockを使用していくつかのテストを書いた.🤩
    あなたのための挑戦の少し?ユーザーの削除、変更、およびユーザーの取得の可能性などの機能を追加します.
    これらのすべての機能を使用してコードを見つけるhere .

    結論


    本稿では,rduxによるtddへの簡単な導入を行った.TDDを使用して反応コンポーネントを書くことを探しているならば、あなたは私が書いたこれをチェックすることができます.
    そして、あなたの提案や質問は、コメントのセクションで歓迎されるようにすべての記事をより良くすることができます.
    記事投稿bloggu.io . 無料でお試しください.