2017-08-08 1 views
0

私のアプリケーションのすべてのロジックは、アクションクリエータ(サンク)に存在します。アクション作成者のロジックの大半は複雑ではなく、ストアからの値である条件付きの条件式で構成されます。この値がストアに存在する場合、これらのアクション作成者をディスパッチし、そうでない場合はこのアクションをディスパッチします。また、いくつかの "集約者"もあります。これは、いくつかの他のアクションクリエイターを派遣するアクションクリエーターであり、しばしばいくつかの状態値の存在に基づいています。および状態からのパラメータで条件付きでAPI抽象化を呼び出すAPIラッパーを使用して、応答を処理します。条件付きロジックとgetState()用法を使用して複合redux thunkをテストする

ポイントは、ほとんどがgetState関数を使用して、引数として受け取るのではなく、必要なものすべてを取得します。さて、このアプローチは私にはうまくやってきましたが、使い方が簡単ではありませんが、私はそれをテストするのに苦労しています。今まで、私はこの提案に続いてすべてのテストを書いた:https://github.com/reactjs/redux/issues/2179。基本的に、私は最初にいくつかの他のアクションを使って必要な状態を設定し、フェッチ呼び出しをモックし、私がテストしようとしたサンクをディスパッチし、その後様々なセレクタを使って状態をチェックします。これは、単一のテストで複数のアクション、レデューサー、セレクターを同時にテストします。私はテストで特定のユースケースを完全に検証しているのが好きですが、実際にはこれが良いかどうかはわかりません。私の主な問題は、他の5人のアクションクリエータを派遣しているため、いくつかのサンクはテストできないということです。状態が変わったかどうかをチェックすることを除いて、少なくともプロミスチェーンが巨大になり、複数のテストで同じ機能を繰り返しテストします。

私はこのテスト全体に慣れていません。インターネット上のすべての例は、TODOリストや他のばかばかしい簡単なCRUDアプリケーションです。これは役に立ちません。複数の状態ノードに依存するアクションクリエータを使用して、多くの条件付きロジックを使用する複雑なアプリケーションで、どのように実際にレキシックステストを実行しますか?

+0

すべてのユースケースでお手伝いできますが、ミドルウェアを使用することを検討しているかどうか不明ですか? –

答えて

0

短い回答:模擬それ。 =)

長い回答:できるだけ多くのテストで実際のコード(すなわち、テストダブルを含まない)を使用することを個人的に好みます。しかし、時にはそれだけの価値がないし、あなたは嘲笑に落ちる必要があります。

  1. いくつかのサブサンクが、実際にテスト中のあなたのサンクから派遣されたこと:あなたは/あなたのテストで確認する必要がある場合がありますいくつかのものがある上記のようなケースで

  2. テスト中のサンクは、ディスパッチするサブサンクの結果を正しく処理します。
  3. テスト中のサンクは、ディスパッチされたサブサンクによって更新された更新された状態を正しく処理します。

あなたが上記のどの組み合わせをテストするかによって、異なる戦略を使用することができます。たとえば、テスト対象のサンクがサブサンクの結果に依存している場合に、サブサンクが送出されたかどうかを確認する必要はありません。特定のサンクのテスト結果の動作に影響する特定のデータを返すように、分かりやすい方法(下記のスニペットの詳細についてはauthモックを参照してください)。

可能性のある模擬戦略を説明するために、次の例を考えてみましょう。認可機能を実装する必要があるとします。あなたのサーバーがhttpエンドポイント経由でユーザーを認証し、成功した場合、後でwebsocket接続を開くために使用される認証トークンを送り返すとしましょう。

connectサンクがあり、ユーザーのログインとパスワードでauthサブサンクを送信し、httpで指定の資格情報を送信します。サーバーが応答するとauthサンクはstoreのトークンを受信して​​格納します(説明の都合上)。 authが解決すると、connectがトークンで他のものを実行するotherStuffサンクをディスパッチします。最後にconnectwsApiでソケット接続を開きます。 authotherStuff:我々はつもりです何

// ======= connect.js ======= 

import { auth, getToken } from './auth'; 
import * as wsApi from './ws'; 
import { otherStuff } from './other-stuff'; 

export const connect = (login, password) => (dispatch, getState) => { 
    // ... 

    return dispatch(auth(login, password)) 
    .then(() => { 
     const token = getToken(getState()); 
     dispatch(otherStuff(token)); 
     wsApi.connect(token); 
    }); 

    // ... 
}; 


// ======= auth.js ======= 

import * as httpApi from './http'; 

const saveToken = token => ({ type: 'auth/save-token', payload: token }); 

export const auth = (login, password) => 
    dispatch => 
    httpApi.login(login, password) 
    .then(token => dispatch(saveToken(token))); 

export const getToken = state => state.auth.token; 

export default (state = {}, action) => action.type === 'auth/save-token' ? { token: action.payload } : state; 


// ======= other-stuff.js ======= 

export const otherStuff = token => (dispatch) => { 
    // ... 
}; 

は2つのサンクを模擬することです。 connectauthに大きく依存していますので、私たちはauthに渡した模擬動作に応じて、authがちょうどチェックconnectの動作によって呼び出されるようにします。 otherStuffの場合は少し複雑です。それが実際に他のディスパッチされたアクションをログに記録するカスタムミドルウェアを実装していないかどうかを確認する方法はありません。 (私はあざけるためjestを使用しています)、次のようにすべてのテストのすべてが表示されます:あなたが与えられたスニペット上の任意のコメントが必要な場合は

import { createStore, applyMiddleware, combineReducers } from 'redux'; 
import thunk from 'redux-thunk'; 
import { connect } from './connect'; 
import { auth, getToken } from './auth'; 
import { otherStuff } from './other-stuff'; 
import * as wsApi from './ws'; 
const authReducer = require.requireActual('./auth').default; 

jest.mock('./auth'); 
jest.mock('./ws'); 
jest.mock('./other-stuff'); 

const makeSpyMiddleware =() => { 
    const dispatch = jest.fn(); 
    return { 
    dispatch, 
    middleware: store => next => action => { 
     dispatch(action); 
     return next(action); 
    } 
    }; 
}; 

describe('connect',() => { 
    let store; 
    let spy; 

    beforeEach(() => { 
    jest.clearAllMocks(); 
    spy = makeSpyMiddleware(); 

    store = createStore(authReducer, {}, applyMiddleware(spy.middleware, thunk)); 

    auth.mockImplementation((login, password) =>() => { 
     if (login === 'user' && password == 'password') return Promise.resolve(); 
     return Promise.reject(); 
    }); 
    }); 

    test('happy path',() => { 
    getToken.mockImplementation(() => 'generated token'); 
    otherStuff.mockImplementation(token => ({ type: 'mocked/other-stuff', token })); 

    return store.dispatch(connect('user', 'password')).then(() => { 
     expect(wsApi.connect).toHaveBeenCalledWith('generated token'); 
     expect(spy.dispatch).toHaveBeenCalledWith({ type: 'mocked/other-stuff', token: 'generated token'}); 
    }); 
    }); 

    test('auth failed',() => { 
    return store.dispatch(connect('user', 'wrong-password')).catch(() => { 
     expect(wsApi.connect).not.toHaveBeenCalled(); 
    }); 
    }); 
}); 

は、お気軽に。

関連する問題