GraphQLを採用したシステムを開発していて、CypressでE2Eテストを行うようにしました。
しかし色々とつまずくところがあったので、同じ問題に直面した方のために残したいと思います。
GraphQLのリクエストをwaitできない
/graphqlがエンドポイントだった場合、以下のように書けると思います。
// 動かない
cy.server()
cy.route('POST', '/graphql').as('graphql')
cy.wait('@graphql')
しかし、このままだとwaitしてくれません。
その原因として、Cypressはfetch API を現在(2019/02/07)サポートしてないために起こっているようです。
(サポート予定: https://github.com/cypress-io/cypress/issues/687)
そのため、Apollo Clientなどの場合は内部ではfetch APIを使用しているようで、上記のコードでは動きません。
解決方法としては、delete
オペレーターを使ってfetchを消してXMLHttpRequest API に戻してやることができるそうです。
index.jsなどで以下を実行する必要があります。
// index.js
Cypress.on('window:before:load', (win) => {
delete win.fetch
})
エンドポイントが一つのためリクエスト毎にaliasを作れない
これはつまずきというか、心配事だったのですが、
一度のテストに複数のリクエストが行われる際、GraphQLだとエンドポイントが一つ(/graphqlなど)なので、
それぞれのリクエストで違うaliasをつけることができません。
// リクエストが全て同じエンドポイント
cy.route('POST', '/graphql').as('query1')
cy.route('POST', '/graphql').as('query2')
これは解決策とかの話ではないですが、
GraqhQLの特徴の一つとして、
1リクエストで欲しいデータが全て取得できるので、
そもそも同じタイミングで複数リクエスト行われる状況にはならないはずです。
そこには必ずフローがあるはずで、GraphQLの場合はエンドポイントが同じでも、どのリクエストをしているのかは必ずわかるはずです。
cy.server()
// ログイン
cy.route('POST', '/graphql').as('loginMutation')
cy.get('input').eq(0).type('taro@example.com')
cy.get('input').eq(1).type('password1234')
cy.get('button').click()
cy.wait('@loginMutation')
// ログイン後一覧データ取得
cy.route('POST', '/graphql').as('postsQuery') // 同じエンドポイントだがこのタイミングでは必ず一度だけリクエストが行われる
cy.wait('@postsQuery')
どうしても同じタイミンングで複数リクエストが発生してaliasつけたい場合は自作コマンドを作って、bodyにあるデータで判定するしかないようです。
(参考: https://stackoverflow.com/questions/53814647/how-can-i-alias-specific-graphql-requests-in-cypress)
テスト毎にlocalStorageがキャッシュされない
GrraphQLの場合JWTなどのトークンベースの認証を使っていると思います。
その場合localStorageにトークンを保存してAuthorizationヘッダーにのせてリクエストしているはずです。
Cypressではテスト毎にlocalStorageがクリアされてしまうので、ログイン状態を保持しておくことができませんでした。
そのため、beforeEach()
で毎回ログイン処理を行っていてテストの実行時間が無駄に長くなっていました。
Cookieの場合は残したいデータをCypressのホワイトリストに入れる ことができます。
localStorageの場合はそのようなAPIが用意されていないため、ワークアラウンドが必要でした。
// index.js
// 保持するトークン
let token
// トークンをlocalStorageにセット
const setToken = () => {
if (token) {
localStorage.setItem('token', token)
}
}
// トークンをキャッシュ
const cacheToken = () => {
token = localStorage.getItem('token')
}
// テスト実行前にトークンをセット
beforeEach(setToken)
// テスト実行後にトークンをキャッシュ
afterEach(cacheToken)
こうすることで、before()
でログイン処理を一度挟めば、その後の全てのテストでログイン状態を保持したままテストが実行できます。
参考: https://github.com/cypress-io/cypress/issues/461
最後に
GraphQLアプリをCypressで実行するにあたって自分のつまったところを紹介しました。
他に、「いいやり方あるよー」、「ここに詰まって、こう解決した」などあれば教えていただきたいです。