Cypress で実際にテストコードを書いてみた時の Tips

Cypress で書くテストコードの Tips の紹介

Leaner Technologies で業務委託している西辻です。
前回の Cypress の設定 の続きになります。
今回は実際に具体的なテストコードを書いていく中での Tips を紹介していきます。

Form に入れる値などは型定義でまとめておくと便利

前回の記事で TypeScript を導入しているので、型を定義していきます。
E2E テストを実施する際に、大抵の場合 CRUD の操作するので利用する値を事前に定義しておくと補完が効いて楽です。

types.ts
export interface ProjectCommentForm {
  body: string
}

export const createdProjectComment: ProjectCommentForm = {
  body: 'Cypress test comment',
}

こんな感じで利用します。

integration/sample_spec.ts
import { createdProject } from 'cypress/types'
...
  it('投稿できる', () => {
    cy.get('[data-cy-form-comment]').within(() => {
      cy.get('button').should('be.disabled')
    })
    cy.get('textarea').type(createdProjectComment.body)
    cy.get('[data-cy-form-comment]').within(() => {
      cy.get('button').should('not.be.disabled').click()
    })
    cy.contains(createdProjectComment.body)
  })

操作は Custom Commands にまとめておくとテストコードの可読性が上がる

Custom Commands という機能があるので、ページごとに行う操作をラッピングしておくと integration 配下の spec が見やすくなります。
https://docs.cypress.io/api/cypress-api/custom-commands#Syntax

support/sample_commands.ts
Cypress.Commands.add('visitProject', () => {
  cy.get('[data-cy-nav-item]').contains('プロジェクト').click()
  cy.get('[data-cy-nav-subitem]').contains('プロジェクトの管理').click()
})

Cypress.Commands.add('createProject', () => {
  cy.contains('新規追加').click()

  cy.get('[data-cy-input-title]').type(createdProject.title)
  cy.get('[data-cy-input-description]').type(createdProject.description)
  cy.contains('some').click()
  // ...様々な処理
  cy.contains(createdProject.title)
})

また、 Custom Commands 内でもアサーションはできるので、各 Custom Commands が期待通りに動いているかまでをチェックしておくと安心です。

sample_spec.ts
describe('プロジェクトのコメント', () => {
  before(() => {
    cy.login()
    cy.visitProject()
    cy.createProject()
  })
})

ページ以外でもシンプルなダイアログの操作なども共通化しておくと再利用できるので便利です。

Custom Commands で補完が効くようにする

上述の Custom Commands ですが、 TypeScript での補完が可能で便利です。
https://docs.cypress.io/guides/tooling/typescript-support#Types-for-custom-commands

support/index.ts
import './sample_commands'
support/index.d.ts
declare namespace Cypress {
  // project
  interface Chainable<Subject> {
    visitProject(): Chainable<Subject>
  }
  interface Chainable<Subject> {
    createProject(): Chainable<Subject>
  }
  interface Chainable<Subject> {
    updateProject(): Chainable<Subject>
  }
  interface Chainable<Subject> {
    destroyProject(title: string): Chainable<Subject>
  }

UI の変更に依存されないように data-cy attribute を利用する

https://docs.cypress.io/guides/references/best-practices#How-It-Works

こちらは Best Practices になるのですが、E2E テストの操作対象となる element には data-cy で名前を付与しておくと変更に強くなります。
UI Component などを利用している場合にも DOM のネストが深くなりがちなので操作する対象には data-cy を付与しておくと cy.get() の可読性が上がります。

E2E テストするにあたって

ここからは Cypress とは直接関係ないのですが、 E2E テストするにあたって準備したことやテストコードを書く上で気をつけた点などを記載していきます。

テストケースを列挙する

当たり前なのですが、 Cypress でテストコードを書く前にテストケースを一覧化します。
ページによっては、データの前準備が必要だったりして、今すぐはテストを書けない部分などについてチーム内で認識を合わせるのに利用します。
また、構造化して書いておくことで、そのまま Cypress のコードに落とし込みやすいというメリットもあります。

冪等性が担保された状態でテストを終わらせる

E2E テスト用にクリーンな環境を用意できるのがベストなのですが、環境を専用に作るのは大変なので、開発環境で代用することもあるでしょう。
今回はまさに上記のパターンだったのですが、なるべく冪等性を保てるようにテストを組み立てました。
例えば、何かデータを追加したら、最後の after でテストが終了する際に、作成したデータを消すようにします。
こうすることでテスト前と同じ状況に戻るので何度実行してもデータ増えたことによって発生するテストのエラーの発生を抑えられます。

https://docs.cypress.io/guides/references/best-practices#Using-after-or-afterEach-hooks

また、 Cypress の Best Practices では状態をクリーンにするため before で login、 after で logout することが推奨されています。

これもログイン状況によって変化する要素でテストが落ちるケースを減らせるので導入しています。

まとめ

実際に Cypress を使って E2E テストを書くにあたっての Tips をまとめてみました。
Cypress は TypeScript のサポートが手厚く、ほぼ全てのコードを TypeScript で記述できるのでテストコードの書き味はなかなか良いです。

宣伝

Leaner Technologies では自動テストを拡充して品質を維持しながらスピード感ある開発を一緒にやっていけるメンバーを探しています!
https://careers.leaner.co.jp/

リーナーテックブログ

Discussion