CypressのRealWorldApp(RWA)を触ってテストコード見てみた

こんばんは🌝

Cypressを最近改めて調べてると、Cypressが公式にブログCypress Real World Appというのを公開していることを知りました、

このwebアプリケーション自体は銀行口座管理ツールなんですが、この中にはCypressの使い方やベストプラクティスなどを盛り込んだCypressの動作デモも含まれており、それを公開することで広くCypressの使い方・ベストプラクティスを知ってもらおうという目的らしいです。

せっかくなので中のコードをざざっとみてみました。

事前準備

webアプリおよびCypressの起動は、書いてある手順通りで問題なく動作します。

  1. Clone: git clone https://github.com/cypress-io/cypress-realworld-app.git
  2. Install: yarn install
  3. Start the app: yarn start
  4. Start testing: yarn cypress:open

https://www.cypress.io/blog/2020/06/11/introducing-the-cypress-real-world-app/

そういえば途中でM1Macの場合はchromiumの手動インストールが必要でした。

最後のyarn cypress:openした後、Cypressのアプリケーションがたちあがります。アプリケーション右上の Run 21 integration specs をクリックするとテストが自動で走り始め、だいたい3分ほど(私のM1MacMiniだと)かかって終了しました、

テストコード見てて思ったこと

cypressディレクトリの構成は以下の通りでした

cypress
├── fixtures
│   └── public-transactions.json
├── global.d.ts
├── plugins
│   └── index.ts
├── support
│   ├── auth-provider-commands
│   ├── commands.ts
│   ├── index.ts
│   └── utils.ts
├── tests
│   ├── api
│   ├── demo
│   ├── ui
│   └── ui-auth-providers
└── tsconfig.json

テストはapi/demo/ui/ui-auth-providersの4津に分割されていて、その中にさらに機能ごとにファイルで分割されていました。

apiディレクトリの中のテストコードで気になったところをかいつまんでいきます。

apiディレクトリのテスト方針

apiディレクトリでは、UIに関するテストは一切含んでなく、APIのGETやPOSTにアクセスしてAPIインタフェースをテストするようなコードになっていました。何をテストしているかというとそこまで細かくはなかったです。

例えば seedを入れた状態でいいねをGETするといいねの配列のlengthが1であったりとか、いいねの記録(POST)が200系でレスポンスされたりとかそんなレベルでした。厳密にレスポンスの型をチェックしてるのかなと思っていたけど、そこまでのこと流行っていませんでした。Cypressでそこまでテストするのは違うのかもしれないですね…😕 OpenAPIでやれよという話でしょうか…

と思ったら一部分では型のチェックもやってましたね…APIの重要性で分けているのかもしれないですね

expect(response.status).to.eq(200);
expect(response.body.transaction.id).to.be.a("string");
expect(response.body.transaction.status).to.eq("complete");
expect(response.body.transaction.requestStatus).to.eq(undefined);

cy.task("db:seed");

この処理でいうdb:seedは、pluginsディレクトリのNode.jsの処理を指しています。このような書き方をすることで、重複する処理を実行していそうです。

実際にこのdb:seedでは、axiosを使ってAPIサーバーにPOSTをしてました。おそらくそれトリガーでseedが読み込まれる仕組みだと思われます。

cy.database("filter", "users")

cypressでDBからデータを取得することまで可能みたいです。

cypress/support/commands.ts#L303で、Cypress.Commands.add()で独自に関数を入れているようです。

プラグインを利用せず独自に関数を追加する理由がいまいち分かってません。。プラグインよりも拡張性が高いのでしょうか..。しかもよく調べると結局はプラグインに処理を委譲してました。少し意図がわかりません…🤔

次にuiディレクトリの中も見てみました。

cy.intercept("POST", "/users").as("signup");

ネットワークリクエストをいじっていそうです。as("alias")でaliasをつけることで、後ほど cy.wait("@alias")のように、特定のネットワークリクエストが完了するまで待つみたいです。

ただ、GraphQLの場合だとGraphQLサーバーへのどのリクエストも同一のパスになってしまうため、エイリアスをつけるために少し回りくどい記述になってそうですね

    cy.intercept("POST", apiGraphQL, (req) => {
      const { body } = req;

      if (body.hasOwnProperty("operationName") && body.operationName === "CreateBankAccount") {
        req.alias = "gqlCreateBankAccountMutation";
      }
    });

cy.getBySel("sidenav-toggle").click();

これも独自コマンドとして追加されていました。

中身は要するに [data-test=${selector}]というセレクタでcy.get()しているだけでした。セレクタにはやっぱり気を使ってdata-testというkeyで全て統一しているようですね

beforeEachについて

uiのどのファイルを見ても、beforeEachでは似たような処理を行なっていました。主に以下のような処理です。

  1. DBへのseedの設定
  2. ネットワークリクエストのalias設定
  3. ログイン処理

1つ気づいたことがありました。E2Eテストでログインした状態でテストを行いたい場合は、(ログインに関するテストでもない限りは)CookieをいじったりlocalStorageをいじったりしてログイン状態を偽装するのが普通かと思っていたんですが、実際にログイン処理を毎度beforeEachで行なっているのは少し驚きました

cy.visualSnapshot("Transaction Navigation Tabs Hidden");

(追記中)

ui-auth-providersディレクトリもついでに少しみてみました。

auth0やcognitoを使ったソーシャルログインに関わるテストコードが記述されていました。中身はlocalStorageをゴリゴリにいじったりとだいぶ複雑なことをやってて解読できませんでした😇。 ソーシャルログインはE2Eがやりにくそうな部分ではあるので、ログインを偽装しているんでしょうか…🤔

所感

  • バックエンド側にdbのseedを設定するだけのエンドポイントが立っていた
    • バックエンド側の十分な協力が必要だと感じた、逆にバックエンドの協力がないととても描きにくいのだろうと思った。
    • 既存のPJにコストをかけずにE2Eテストを入れることもできると思うが、正常系のみだったり、ビジネスのコアになる部分のみだったりの範囲がコスパに見合う範囲なのだろうと感じた
  • ソーシャルログイン、E2Eテストしたくないなぁと思った
    • ui-auth-providersディレクトリを見て思ったのは、もし案件で導入しすると、ソーシャルログイン部分のテストコードは他人が見ても理解するのに多大な時間がかかってしまいそうだし、変なクセのある処理が多く出てきてメンテナンスに多大なコストがかかりそうな予感がした

See also