Redux での非同期サンク アクションのテスト
どのテストでも setSubscribed redux-thunk アクション作成者を呼び出していません。代わりに、同じタイプの新しいアクションを定義し、テストでそれをディスパッチしようとしています。
両方のテストで、次のアクションが同期的にディスパッチされています。
const subscribed = { type: 'SET_SUBSCRIBED', subscribed: true }
このアクションでは、どの API に対してもリクエストは行われません。
外部 API からフェッチして、成功または失敗時にアクションをディスパッチできるようにしたいと考えています。
将来のある時点でアクションをディスパッチするため、setSubscribe さんくアクション作成者を使用する必要があります。
redux-thunk の仕組みを簡単に説明した後、このサンク アクション クリエーターをテストする方法を説明します。
アクション vs アクションクリエーター
おそらく、アクション クリエータは、呼び出されたときにアクション オブジェクトを返す関数であると説明する価値があります。
アクションという用語は、オブジェクト自体を指します。このアクション オブジェクトの唯一の必須プロパティは、文字列である必要がある type です。
たとえば、ここにアクションクリエーターがあります。
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
オブジェクトを返す関数です。プロパティの 1 つが type と呼ばれるため、このオブジェクトが redux アクションであることがわかります。
オンデマンドで追加する toDo を作成します。犬の散歩を思い出させる新しい todo を作成しましょう。
const walkDogAction = addTodo('walk the dog')
console.log(walkDogAction)
*
* { type: 'ADD_TO_DO, text: 'walk the dog' }
*
この時点で、アクション作成者によって生成されたアクション オブジェクトができました。
このアクションをレデューサーに送信してストアを更新する場合は、アクション オブジェクトを引数としてディスパッチを呼び出します。
store.dispatch(walkDogAction)
偉大な。
オブジェクトをディスパッチすると、レデューサーに直接送られ、犬の散歩を思い出させる新しい todo でストアが更新されます。
より複雑なアクションを作成するにはどうすればよいでしょうか? アクションの作成者に、非同期操作に依存する何かを実行させたい場合はどうすればよいでしょうか。
同期 vs 非同期 Redux アクション
async (非同期) と sync (同期) とはどういう意味ですか?
何かを同期的に実行するときは、それが完了するのを待って
から別のタスクに進みます。何かを非同期で実行すると、完了する前に別のタスクに移ることができます。
では、犬に何かを持ってきてもらいたい場合はどうすればよいですか? この場合、私が気にすることは3つあります
- 私が彼にオブジェクトをフェッチするように頼んだとき
- 彼は何かをうまく取得しましたか?
- 彼はオブジェクトを取得できませんでしたか? (つまり、スティックなしで私に戻ってきた、一定時間後にまったく戻ってこなかった)
犬を散歩させるための addtodo アクションのような単一のオブジェクトでこれを表現する方法を想像するのはおそらく難しいでしょう。
アクションはオブジェクトではなく、関数である必要があります。なぜ関数?関数を使用して、さらにアクションをディスパッチできます。
fetch の大きな包括的なアクションを 3 つの小さな同期アクションに分割します。メインのフェッチ アクション クリエーターは非同期です。このメイン アクション クリエータはアクション自体ではなく、さらなるアクションをディスパッチするためにのみ存在することに注意してください。
サンク アクション クリエーターはどのように機能しますか?
本質的に、サンク アクション クリエーターは、オブジェクトではなく関数を返すアクション クリエーターです。redux-thunk をミドルウェア ストアに追加すると、これらの特別なアクションがストアのディスパッチ メソッドと getState メソッドにアクセスできるようになります。
Here is the code inside Redux thunk that does this:
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
setSubscribe 関数は、ディスパッチを引数として受け取る関数を返すという署名に従っているため、サンク アクションの作成者です。
これが、サンク アクション クリエータが関数を返す理由です。この関数はミドルウェアによって呼び出され、ディスパッチにアクセスして状態を取得できるため、後日さらにアクションをディスパッチできます。
アクションを使用した非同期操作のモデル化
私たちの行動を書きましょう。redux サンク アクション作成者は、非同期アクションのライフサイクルを表す他の 3 つのアクションを非同期的にディスパッチする責任があります。この場合は http 要求です。このモデルは、成功またはエラー (失敗) を示す開始と結果が必ず存在するため、すべての非同期アクションに適用されることを覚えておいてください。
アクション.js
export function fetchSomethingRequest () {
return {
type: 'FETCH_SOMETHING_REQUEST'
}
}
export function fetchSomethingSuccess (body) {
return {
type: 'FETCH_SOMETHING_SUCCESS',
body: body
}
}
export function fetchSomethingFailure (err) {
return {
type: 'FETCH_SOMETHING_FAILURE',
err
}
}
export function fetchSomething () {
return function (dispatch) {
dispatch(fetchSomethingRequest())
return fetchSomething('http://example.com/').then(function (response) {
if (response.status !== 200) {
throw new Error('Bad response from server')
} else {
dispatch(fetchSomethingSuccess(response))
}
}).catch(function (reason) {
dispatch(fetchSomethingFailure(reason))
})
}
}
おそらくご存知のとおり、最後のアクションは redux thunk アクション クリエーターです。これは、関数を返す唯一のアクションであるためです。
Mock Redux ストアの作成
テスト ファイルで、redux-mock-store ライブラリから configure store 関数をインポートして、偽のストアを作成します。
import configureStore from 'redux-mock-store';
このモック ストアは、テストで使用される配列内のディスパッチされたアクションになります。
サンク アクション クリエーターをテストしているため、モック ストアはテストで redux-thunk ミドルウェアを使用して構成する必要があります。そうしないと、ストアがサンク アクション クリエーターを処理できません。つまり、オブジェクトの代わりに関数をディスパッチできなくなります。
const middlewares = [ReduxThunk];
const mockStore = configureStore(middlewares);
Out mock store には store.getActions メソッドがあり、これを呼び出すと、以前にディスパッチされたすべてのアクションの配列が返されます。
次に、テスト アサーションを作成して、モック ストアにディスパッチされる予定だった実際のアクションと、予想されるアクションを比較します。
Mocha でサンク アクション クリエーターから返された promise をテストする
したがって、テストの最後に、サンク アクション クリエーターをモック ストアにディスパッチします。サンク アクション作成者によって返された promise が解決されたときにアサーションが .then ブロックで実行されるように、このディスパッチ呼び出しを返すことを忘れてはなりません。
動作テスト
上記のアクションを使用してこのテスト ファイルをアプリにコピーし、すべてのパッケージをインストールして、以下のテスト ファイルのアクションを適切にインポートすると、redux thunk アクション クリエーターが確実にディスパッチされるようにテストする実際の例が得られます。正しい行動。
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import fetchMock from 'fetch-mock' // You can use any http mocking library
import expect from 'expect' // You can use any testing library
import { fetchSomething } from './actions.js'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('Test thunk action creator', () => {
it('expected actions should be dispatched on successful request', () => {
const store = mockStore({})
const expectedActions = [
'FETCH_SOMETHING_REQUEST',
'FETCH_SOMETHING_SUCCESS'
]
// Mock the fetch() global to always return the same value for GET
// requests to all URLs.
fetchMock.get('*', { response: 200 })
return store.dispatch(fetchSomething())
.then(() => {
const actualActions = store.getActions().map(action => action.type)
expect(actualActions).toEqual(expectedActions)
})
fetchMock.restore()
})
it('expected actions should be dispatched on failed request', () => {
const store = mockStore({})
const expectedActions = [
'FETCH_SOMETHING_REQUEST',
'FETCH_SOMETHING_FAILURE'
]
// Mock the fetch() global to always return the same value for GET
// requests to all URLs.
fetchMock.get('*', { response: 404 })
return store.dispatch(fetchSomething())
.then(() => {
const actualActions = store.getActions().map(action => action.type)
expect(actualActions).toEqual(expectedActions)
})
fetchMock.restore()
})
})
Redux サンク アクション クリエーターはアクション自体ではなく、さらなるアクションをディスパッチするためにのみ存在するため、覚えておいてください。
サンク アクション クリエーターのテストの多くは、特定の条件下でどのような追加アクションがディスパッチされるかについてアサーションを行うことに重点を置いています。
これらの特定の条件は、非同期操作の状態であり、タイムアウトした HTTP 要求または成功を表す 200 ステータスである可能性があります。
Redux Thunk をテストする際のよくある問題- Action Creators で Promise を返さない
アクション クリエーターに promise を使用する場合は、アクション クリエーターによって返される関数内で promiseを返すことを常に確認してください。
export function thunkActionCreator () {
return function thatIsCalledByreduxThunkMiddleware() {
// Ensure the function below is returned so that
// the promise returned is thenable in our tests
return function returnsPromise()
.then(function (fulfilledResult) {
// do something here
})
}
}
そのため、最後のネストされた関数が返されない場合、その関数を非同期で呼び出そうとすると、エラーが発生します。
TypeError: Cannot read property 'then' of undefined - store.dispatch - returns undefined
これは、promise が .then 節で実行または拒否された後にアサーションを作成しようとしているためです。ただし、.then は promise でのみ呼び出すことができるため、機能しません。プロミスを返すアクションクリエーターで最後にネストされた関数を返すのを忘れたので、 undefined で .then を呼び出します。未定義の理由は、関数のスコープ内に return ステートメントがないためです。
そのため、呼び出されたときにプロミスを返すアクションクリエーターでは、常に return 関数を使用してください。