質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
Redux

Reduxは、JavaScriptアプリケーションの状態を管理するためのオープンソースライブラリです。ReactやAngularで一般的にユーザーインターフェイスの構築に利用されます。

ソフトウェアテスト

ソフトウェアテストは、プログラムを実行し、要求通りに正しく動作が行えているかどうか確認する作業です。プログラム中のバグをできる限り多く発見することを目標として行われます。

ユニットテスト

ユニットテストは、システムのテスト手法の一つで、個々のモジュールを対象としたテストの事を指します。対象のモジュールが要求や性能を満たしているか確認する為に実行します。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

0回答

1230閲覧

【React+Redux+TypeScript】コンポーネントのテストで、createAsyncThunkで作成した関数を呼び出せない。

masa_iss_mago

総合スコア0

Redux

Reduxは、JavaScriptアプリケーションの状態を管理するためのオープンソースライブラリです。ReactやAngularで一般的にユーザーインターフェイスの構築に利用されます。

ソフトウェアテスト

ソフトウェアテストは、プログラムを実行し、要求通りに正しく動作が行えているかどうか確認する作業です。プログラム中のバグをできる限り多く発見することを目標として行われます。

ユニットテスト

ユニットテストは、システムのテスト手法の一つで、個々のモジュールを対象としたテストの事を指します。対象のモジュールが要求や性能を満たしているか確認する為に実行します。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2021/03/27 08:21

前提・実現したいこと

ReduxのcreateAsyncThunkで作成した、API通信関数を、
コンポーネントのテスト内で呼び出すことができません。

具体的には、
expect(store.dispatch).toHaveBeenCalledWith();
の、toHaveBeenCalledWithの引数内で呼び出したいです。

解決法をご教示いただけますと幸いでございます。

発生している問題・エラーメッセージ

● <Auth /> › Register mode test expect(jest.fn()).toHaveBeenCalledWith(...expected) Expected: [Function anonymous] Received 1: {"payload": undefined, "type": "user/toggleMode"} 2: [Function anonymous] 3: [Function anonymous] Number of calls: 4 95 | expect(store.dispatch).toHaveBeenCalledTimes(4); 96 | expect(store.dispatch).toHaveBeenCalledWith(toggleMode()); > 97 | expect(store.dispatch).toHaveBeenCalledWith(fetchAsyncRegister(cred)); | ^ 98 | }); 99 | }); 100 | at Object.<anonymous> (src/features/user/Auth.test.tsx:97:28)

該当のソースコード

以下は、テストに用いるための、前提となるコード群です。

//types.ts export interface CRED { email: string; password: string; } export interface JWT { token: string; } export interface USER { uuid: string; email: string; } export interface PROFILE { id: number; user_id: string; name: string; image: string; } export interface USER_STATE { isLoginView: boolean; }
//userSlice.ts export const fetchAsyncLogin = createAsyncThunk( "user/login", async (auth: CRED) => { const res = await axios.post<JWT>( `${process.env.REACT_APP_API_URL}/login`, auth, { headers: { "Content-Type": "application/json", }, } ); return res.data; } ); export const fetchAsyncRegister = createAsyncThunk( "user/register", async (auth: CRED) => { const res = await axios.post<USER>( `${process.env.REACT_APP_API_URL}/signup`, auth, { headers: { "Content-Type": "application/json", }, } ); return res.data; } ); export const fetchAsyncCreateProf = createAsyncThunk( "user/createProfile", async () => { const res = await axios.post<PROFILE>( `${process.env.REACT_APP_API_URL}/user/profile`, { image: null }, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${localStorage.localJWT}`, }, } ); return res.data; } ); const initialState: USER_STATE = { isLoginView: true, }; export const userSlice = createSlice({ name: "user", initialState, reducers: { toggleMode(state) { state.isLoginView = !state.isLoginView; }, }, extraReducers: (builder) => { builder.addCase( fetchAsyncLogin.fulfilled, (state, action: PayloadAction<JWT>) => { localStorage.setItem("localJWT", action.payload.token); } ); }, }); export const { toggleMode, } = userSlice.actions; export const selectIsLoginView = (state: RootState) => state.user.isLoginView; export default userSlice.reducer;
//Auth.tsx const Auth: React.FC = () => { const classes = useStyles(); const dispatch: AppDispatch = useDispatch(); const isLoginView = useSelector(selectIsLoginView); const [credential, setCredential] = useState({ email: "", password: "" }); const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const value = e.target.value; const name = e.target.name; setCredential({ ...credential, [name]: value }); }; const login = async () => { if (isLoginView) { await dispatch(fetchAsyncLogin(credential)); } else { await dispatch(fetchAsyncRegister(credential)); await dispatch(fetchAsyncLogin(credential)); await dispatch(fetchAsyncCreateProf()); } }; return ( <Container component="main" maxWidth="xs"> <CssBaseline /> <div className={classes.paper}> <Avatar className={classes.avatar}> <LockOutlinedIcon /> </Avatar> <Typography component="h1" variant="h5"> {isLoginView ? "Login" : "Register"} </Typography> <div className={classes.form}> <TextField variant="outlined" margin="normal" required fullWidth label="Email" type="text" name="email" value={credential.email} onChange={handleInputChange} data-testid="email" /> <TextField variant="outlined" margin="normal" required fullWidth label="Password" type="password" name="password" value={credential.password} onChange={handleInputChange} data-testid="password" /> <Button type="submit" fullWidth variant="contained" color="primary" className={classes.submit} onClick={login} data-testid="loginButton" > {isLoginView ? "Login" : "Register"} </Button> <Grid container alignItems="center" justify="center" className={classes.toggle} > <Grid item> <Link variant="body2" onClick={() => dispatch(toggleMode())} data-testid="toggleButton" > {isLoginView ? "Create new account ?" : "Back to Login"} </Link> </Grid> </Grid> </div> </div> </Container> ); }; export default Auth;
//testTools.ts import { configureStore, EnhancedStore } from "@reduxjs/toolkit"; import userReducer from "./user/userSlice"; import boardReducer from "./board/boardSlice"; const makeStore = () => { const store: EnhancedStore = configureStore({ reducer: { user: userReducer, board: boardReducer, }, }); return store; }; export const makeTestStore = () => { const store = makeStore(); const origDispatch = store.dispatch; store.dispatch = jest.fn(origDispatch); return store; };

そして、以下がテストコードになります。

//Auth.test.tsx const cred: CRED = { email: "test@gmail.com", password: "test" }; describe("<Auth />", () => {  it("Register mode test", async () => { const store = makeTestStore(); render( <Provider store={store}> <Auth /> </Provider> ); const emailInputField: HTMLInputElement = screen .getByTestId("email") .querySelector("input") as HTMLInputElement; const passwordInputField: HTMLInputElement = screen .getByTestId("password") .querySelector("input") as HTMLInputElement; const loginButton = screen.getByTestId("loginButton") as HTMLElement; const toggleButton = screen.getByTestId("toggleButton") as HTMLElement; expect(emailInputField).toBeTruthy; expect(passwordInputField).toBeTruthy; expect(loginButton).toBeTruthy; expect(toggleButton).toBeTruthy; //ここまでは全て成功。要素の取り出しはできている。 userEvent.type(emailInputField, cred.email); expect(emailInputField.value).toEqual("test@gmail.com"); userEvent.type(passwordInputField, cred.password); expect(passwordInputField.value).toEqual("test"); userEvent.click(toggleButton); userEvent.click(loginButton); await new Promise((resolve) => setTimeout(resolve, 3000)); expect(store.dispatch).toHaveBeenCalledTimes(4);   /*これも成功。useDispatchが問題なく作動していること、 そしてそれらがボタンのクリックで起動することが確認できた。 */ expect(store.dispatch).toHaveBeenCalledWith(toggleMode());   //これも成功。createSliceのreducerは呼び出せている。 expect(store.dispatch).toHaveBeenCalledWith(fetchAsyncRegister(cred));   //これが通らない…。 }); });

試したこと

toHaveBeenCalledWithの引数内を、{type: "user/login"}や、
{type: fetchAsyncLogin.fulfilled.type}にしても、うまくいきません。
{"type": "user/toggleMode"}を引数にすると通りますが…。

補足情報(FW/ツールのバージョンなど)

nodeのバージョン:v12.18.4
yarnのバージョン:1.22.4

また、testTools.ts内の関数は、下記を参考にしております。
https://blog.krawaller.se/posts/unit-testing-react-redux-components/

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだ回答がついていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問