CypressとPercelとFirebaseとElmでATDD

前回の[CypressとPercelとElmでATDD](https://qiita.com/ababup1192/items/81be40a9f157c211e983)にFirebaseをくっつけました。ほとんど変わりはありませんが、外部に状態を持つような場合にはcypressではどのようにするか気になり試してみました。

今回の[サンプルコード](https://github.com/ababup1192/elm-cypress-firebase-counter)です。

## パフォーマンスを落とさない注意

cypressのサンプルコードから見てみましょう。前回との違いは、`beforeEach`を試す場合には、`describe`のネストがあると全てのテストケースのときに走らなくなってしまうため、直下は`it`にしました。

```counter_spec.js
describe("Elm Counter Test", function() {
  const counterButtonElementId = '[data-cypress-id="counter-num"]';

  beforeEach(function() {
    cy.visit("http://localhost:1234/");

    cy.get(counterButtonElementId).should("not.have.text", "Now loading");
    cy.contains("reset").click();
  });

  it("Increment number", function() {
    cy.contains("+").click();

    cy.get(counterButtonElementId).should("have.text", "1");
  });

  it("Decrement number", function() {
    cy.contains("-").click();

    cy.get(counterButtonElementId).should("have.text", "-1");
  });
});
```

visitは前と変わりませんが、counterの値が"Now loading"ではないことをテストしています。Seleniumに慣れている場合、`wait`, `sleep`のようなものを使ってしまいがちですが(実際に最初にそうしてしまいました)、cypressのassertは即時実行ではなく値が一致しない場合には自動的にwaitを挟んでくれます。つまり一致するまで待ってくれるようになるわけです。

<img width="497" alt="スクリーンショット 2020-01-01 21.55.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48687/7a35cbed-980b-f917-211b-4427be562c8b.png">

また新たに`reset`ボタンを配置しました。これはfirestoreの中身を0で初期化する作用を持っています。実際のサービスではこのようなボタンを設置することはできません。任意のコマンドを実行する`cy.exec`と言う命令を使ってみましたが、cliからfirestoreの中身を初期化するのが遅くE2Eとして遅くなりすぎてしまうためresetボタンを設置しました。実務でCIを回すことを考えるとテストケースごとにDBを初期化するのは現実的では無い気がするので、初期化は全てのテストで1回 あとはアカウントを切り替えるなどの工夫が必要そうです。

## 実装

以前、FirebaseとElmの記事は書いたことがあるため説明を省略します。"Now loading"の部分のみ抜粋してソースコードを載せます。


```Main.elm
type alias Model =
    Maybe Int


init : () -> ( Model, Cmd Msg )
init _ =
    ( Nothing, Cmd.none )

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Increment ->
            case model of
                Just value ->
                    ( Just <| value + 1, setValue <| value + 1 )

                Nothing ->
                    ( model, Cmd.none )

        Decrement ->
            case model of
                Just value ->
                    ( Just <| value - 1, setValue <| value - 1 )

                Nothing ->
                    ( model, Cmd.none )


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Decrement ] [ text "-" ]
        , div [ attribute "data-cypress-id" "counter-num" ]
            [ text
                (case model of
                    Just value ->
                        String.fromInt value

                    Nothing ->
                        "Now loading"
                )
            ]
        , button [ onClick Increment ] [ text "+" ]
        , div [] []
        , button [ onClick Reset ] [ text "reset" ]
        ]
```

updateのIncrementやDecrementは、Modelがあるかないかで分岐をし、ある場合のみ表示やFirestoreの更新をしています。

## まとめ

Firebaseを使って簡単に状態を持つアプリケーションのATDDをしてみましたが、やはり気にすることと言う意味での難易度はグッと上がります。特にE2Eは実行が遅くなりすぎてしまうことが問題として挙げられるため、なるべくパフォーマンスが出るようなテストを心がけたいと思います。しかし、E2Eの効果は絶大で普段だったら中々できないFirebaseを操作するJavaScript側のコードのリファクタリングが安心して行えました。準備や維持が難しい分、強い恩恵が得られるため上手く付き合って行けたらなと思います。