ElmのCmdをスタブする

[ElmのHttp処理部をスタブする](https://qiita.com/ilyaletre/items/a4c8de74a0a4f5e1e51a)
に対するアンサーです

# 要約
- 元のMain.elmをスタブしたいって理由で変更入れたくない
- updateから返却されるcmdを入れ替える

# 問題

> Elmでアプリケーション書いているとリモートのWeb APIへリクエストを飛ばす部分がでてくる。
> けれどチュートリアルの章を読むとHttpパッケージの関数を直接埋め込んで埋め込んである。この流儀に従ってそのまま実装していくと、elm-reactorで動かした際に、Web APIのエンドポイントに本当にリクエストを飛ばしてしまう。
> Web APIの実装がまだない場合や、テストでダミーのJSONを渡したい場合はこれだと困る。

https://qiita.com/ilyaletre/items/a4c8de74a0a4f5e1e51a

Httpに限らず、特定のCmdの結果をデバッグ目的で変えたいという要求もあります。かもしれません

一年越しですが考えてみたので書いておきます

# Main.elm
https://github.com/miyamoen/elm-dev-main-example

[動作確認](https://miyamoen.github.io/elm-dev-main-example/)
これが元のMain.elmになります
1行目にModelをtoStringしたものを表示させています
Fetchボタンを押すとrequestが飛んでついでに乱数を発生させてModelに突っ込んでいます

実際Fetchしてくるのはfetch関数です

```elm
 fetch : Cmd Msg
 fetch =
    Task.succeed (Err Http.NetworkError)
        |> Task.perform Response
```

APIサーバーが用意できていないので失敗する、という体で書いています
実際はAPIを叩きに行っていると思ってください

fetchを読んでいるところは

```elm
 update : Msg -> Model -> ( Model, List (Cmd Msg) )
 update msg model =
     case msg of
         Fetch ->
             model => [ roll, fetch ]
         -- ...
```

Fetchはボタンを押されたときにでるMsg、rollは乱数を発生させるCmdです

ところでupdateの型がいつもと違いますね

## NoRedInk/rocket-update
[package](http://package.elm-lang.org/packages/NoRedInk/rocket-update/latest)
NoRedInkのrocket-updateをよく使っています
`(=>), batchUpdate, batchInit`が含まれています
`(=>)`はおなじみのタプルを作る関数です

`batchUpdate, batchInit`はupdateとinitをそれぞれこの型で書いて

```elm
 update : Msg -> Model -> ( Model, List (Cmd Msg) )
 init : ( Model, List (Cmd Msg) )
```
main関数で使うといい感じになるやつです


```elm
     Html.program
         { init = init |> batchInit
         , view = view
         , update = update >> batchUpdate
         , subscriptions = subscriptions
         }
```

Cmd.batch( = (!))を使わずに済ませることができます

このパッケージの使用は好みの問題なのですが、今回の問題の解決方法的にbatchUpdateを使っておくといい感じになるので使ってます

# DevMain.elm
[動作確認](https://miyamoen.github.io/elm-dev-main-example/dev)
本題です。Main.elmをスタブります（あんまりスタブの意味は分かっていません

とりあえずdev用のMainファイルを用意します。（Mainファイルを分けること自体は元記事でも言及されています
https://github.com/miyamoen/elm-dev-main-example/blob/master/DevMain.elm

DevMainでは基本的にMainのinit, update, view, subscriptonsをmainに入れているだけです
が、updateだけいじっています


```elm
 devUpdate : Msg -> Model -> ( Model, List (Cmd Msg) )
 devUpdate msg model =
     case msg of
         Fetch ->
             let
                 ( new, cmds ) =
                     update msg model
 
                 newCmds =
                     List.indexedMap updateFetchCmd cmds
             in
                 new => newCmds
 
         _ ->
             update msg model
```

devUpdateは通常のupdateと同様にmsgをcase式でパターンマッチしています
今回はFetchからでるCmdをスタブしたいのでFetchブランチだけ特殊な処理をし、それ以外のときはそのままMainのupdateに処理してもらっています


```elm
 ( new, cmds ) =
     update msg model
```
FetchブランチもまずはMain.updateに処理してもらいます
newはそのまま返します。


```elm
 newCmds =
     List.indexedMap updateFetchCmd cmds

 updateFetchCmd : Int -> Cmd Msg -> Cmd Msg
 updateFetchCmd idx cmd =
     case idx of
         0 ->
             Random.int 1 2
                 |> Random.generate Dice
 
         1 ->
             Ok
                 { id = "dummy id"
                 , name = "dummy name"
                 }
                 |> Task.succeed
                 |> Task.perform Response
 
         _ ->
             cmd
```

cmdsの中は`[ roll, fetch]`です
indexedMapでそれぞれ置き換えます

2番目のfetchをdummy userを返すCmdに書き換えました
これでどんなにFetchボタンを押そうが、requestは飛ばずにデータが返ってきます

この方法でやると、Httpに限らず他のCmdも書き換えられます。0ブランチではrollを1か2の乱数を出すCmdに書き換えています

開発中にCmdの結果をいじって挙動を確認することができるようになりましたね

## なんでrocket-updateを使っているか
補足です
Cmd.batchしちゃうと分解不可なのでCmd一つ一つ置き換えるためにはrocket-updateを使っていると楽
もちろん使わなくてもCmd置き換えはできるんですが、複数同時にCmd出していると置き換えめんどうなので

# まとめ
- 元のコードはいじらない
- Cmdは置き換えられる

今回はupdateだけでしたが他のinit, subscriptions, viewもいじいじできるし、なんだったらModelやMsgもついでに拡張してデバッグモード起動とかもできると思います（そこまでいくとコンポーネント的な話になってきますね

新しい概念を導入せず普段のElmコードの延長で書けるので特に難しいことはなかったと思います
Elm runtimeにCmd渡す前に割り込むだけです！

## そもそも
APIを叩けるスタブサーバー立てたらいいのでは