FluxをJest以外でテストする

[Testing Flux Applications](http://facebook.github.io/react/blog/2014/09/24/testing-flux-applications.html)では、Storeをテストする際のトリックとしてJest上で以下のコードを実行している。

```js
var mockRegister = MyDispatcher.register;
var mockRegisterInfo = mockRegister.mock;
var callsToRegister = mockRegisterInfo.calls;
var firstCall = callsToRegister[0];
var firstArgument = firstCall[0];
var callback = firstArgument;
```

Jestは基本全てがモックされるので、要は「Dispatcherの`register`に与えられた引数が観測できるから、Storeのcallbackを捕捉してテストしたい内容に合わせて適宜実行せよ」ということである。

この動作、SinonのSpyにも同じような実装がある。`spy.firstCall`や`spy.getCall`が定義されており、n度目に呼ばれた際に渡された引数を取得できる。例えば以下のコードでMyStore内部に定義したcallbackは取得できる。

```coffeescript
register = spy require('../../src/dispatcher/AppDispatcher'), 'register'
MyStore = require '../../src/stores/MyStore'
callback = register.lastCall.args[0]
```

Storeは常に単一のインスタンスであることを強制されるので、テストごとに状態をリセットしないと前の状態を保持してしまう。Storeの状態を直接変えたりsetterを作るのは禁じ手なので、テストごとに`require`が新たなStoreを返すようにする。

`require`すると通常の場合結果が`require.cache`にキャッシュされるので、`require.cache`から削除することで、再度`require`したとき新しいStoreが返ってくるようにできる。

```coffeescript
delete require.cache[require.resolve '../../src/stores/MyStore']
```

## 実際に動かす

[Jestで書かれたFlux TodoMVCのテスト](https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/stores/__tests__/TodoStore-test.js)をMocha, power-assertで動くように書く。

```bash
$ npm install mocha power-assert sinon espower-coffee --save-dev
```

```coffeescript:test/stores/TodoStore.test.coffee
assert = require 'power-assert'
{spy} = require 'sinon'

describe 'TodoStore', ->
  TodoConstants = require '../../js/constants/TodoConstants'
  register = null
  TodoStore = null
  callback = null

  actionTodoCreate =
    actionType: TodoConstants.TODO_CREATE,
    text: 'foo'
  actionTodoDestroy =
    actionType: TodoConstants.TODO_DESTROY,
    id: 'replace me in test'

  before ->
    register = spy require('../../js/dispatcher/AppDispatcher'), 'register'

  beforeEach ->
    delete require.cache[require.resolve '../../js/stores/TodoStore']
    TodoStore = require '../../js/stores/TodoStore'
    callback = register.lastCall.args[0]

  it 'creates a to-do item', ->
    callback actionTodoCreate
    all = do TodoStore.getAll
    keys = Object.keys all
    assert.equal keys.length, 1
    assert.equal all[keys[0]].text, 'foo'

  it 'destroys a to-do item', ->
    callback actionTodoCreate
    all = do TodoStore.getAll
    keys = Object.keys all
    assert.equal keys.length, 1
    actionTodoDestroy.id = keys[0]
    callback actionTodoDestroy
    assert.equal all[keys[0]], undefined
```

```bash
$ ./node_modules/.bin/mocha --require espower-coffee/guess test/**/*.test.coffee


  TodoStore
    ✓ creates a to-do item
    ✓ destroys a to-do item


  2 passing (100ms)
```

[Jest(Contextify)がsandboxを作る](http://qiita.com/Misumi_Rize/items/4251f4ac993345e7ba68)のよりも比較的低コストでテストができる。

```bash
$ time ./node_modules/.bin/mocha --require espower-coffee/guess test/stores/TodoStore.test.coffee
./node_modules/.bin/mocha --require espower-coffee/guess   0.69s user 0.09s system 99% cpu 0.787 total

$ time ./node_modules/.bin/jest js/stores/__tests__/TodoStore-test.js
./node_modules/.bin/jest js/stores/__tests__/TodoStore-test.js  0.42s user 0.08s system 44% cpu 1.119 total
```