読者です 読者をやめる 読者になる 読者になる

AngularJSのProtractorでngMockE2Eの$httpBackentを使ってテストを書く際に気をつけること

JavaScript

まだ理解が浅い模様。

注意というかexpectとwhenの違いの話。

前提

  • AngularJSにはngMockE2EというE2E用のモックが用意されており、こちらを使用することにより、サーバを用意しなくてもモックでテストが書ける。

* ngMockE2Eの$httpBackendにはngMockの$httpBackendと違って、expect系はなくwhen系のメソッドしかない

問題

よくある入力フォームのE2EテストをAPIの呼び出し部分にモックを使って書いていて、正常な登録と、エラー表示をまとめてテストしようとして、うまくいかなかった。

# sign_in_spec.coffee
"use strict"

describe "ユーザ登録画面", ->
  beforeEach ->
    browser.addMockModule("httpBackendMock", ->
      angular.module("httpBackendMock", ["ngMockE2E"])
      .run(($httpBackend) ->
        $httpBackend.whenPOST("/api/sign_in",
          {
            user:
              email: "test@example.com"
              password: "password"
                password_confirmation: "password"
          }
        ).respond(201, {
            "user":
              "id": 480
          })
        $httpBackend.whenPOST("/api/sign_in",
          {
            user:
              email: "test@example.com"
              password: "password"
              password_confirmation: "12345678"
          }
        ).respond(422, {
            "status": 422,
            "messages": [
              "パスワード再確認とパスワードの入力が一致しません。"
            ]
          })
        $httpBackend.when("GET", /.*/).passThrough()
      )
    )

  beforeEach ->
    browser.get("/sign_in")

  it "エラー確認、登録後、別画面に遷移", ->
    email = element(By.model("registration.email"))
    password = element(By.model("registration.password"))
    password_confirmation = element(By.model("registration.password_confirmation"))
    button = element(By.tagName("button"))
    email.sendKeys("test")
    error = element(By.css(".form-group span:not([class=ng-hide])")).getText()
    expect(error).toBe "メールアドレスを入力してください"
    email.clear()
    error = element(By.css(".form-group span:not([class=ng-hide])")).getText()
    expect(error).toBe "メールアドレスは必須です"
    email.sendKeys("test@example.com")
    # .....省略.......
    password_confirmation.sendKeys("12345678")
    # この時点では、passwordとpassword_confirmationが違う事により失敗のメッセージのレスポンスを期待する
    button.click()
    expect(errorElement.getText())toBe "パスワード再確認とパスワードの入力が一致しません。"
    # .....省略.......
    # 正常系
    button.click()
    expect(browser.getLocationAbsUrl()).toMatch "#/other_page"
    

api/sign_inへのPOSTのバックエンド定義を2つ書いている。
ユーザパラメータの違いにより、使われるモックが変わることを期待しているが、片方しか呼ばれない。
これは考えてみれば当たり前で、whenはexpectと違って、定義されたリクエストが行われた場合に用意されたレスポンスを返し、リクエストを厳密にアサートしないバックエンドを定義するものだから。 パラメータやヘッダーなど、このようにリクエストしてほしいというアサーションを期待するのはexpect系を使わないと書けない。

ちなみに $httpBackend.when("GET", /.*/).passThrough()で、モック以外の通信(この場合、静的なHTMLの取得)を全てサーバと通信するようにしている。

解決

ngMockE2Eの$httpBackendにはexpectがないので、 正常系と失敗系でdescribeを分けて、それぞれにmockを定義してテストを行った。

# sign_in_spec.coffee
"use strict"

describe "ユーザ登録画面", ->
  beforeEach ->
    browser.addMockModule("httpBackendMock", ->
      angular.module("httpBackendMock", ["ngMockE2E"])
      .run(($httpBackend) ->
        $httpBackend.whenPOST("/api/sign_in",
          {
            user:
              email: "test@example.com"
              password: "password"
              password_confirmation: "password"
          }
        ).respond(201, {
            "user":
              "id": 480
          })
        $httpBackend.when("GET", /.*/).passThrough()
      )
    )

  beforeEach ->
    browser.get("/sign_up")

  it "登録後、別画面に遷移", ->
    # test code here

describe "ユーザ登録画面error", ->
  beforeEach ->
    browser.addMockModule("httpBackendMock", ->
      angular.module("httpBackendMock", ["ngMockE2E"])
      .run(($httpBackend) ->
        $httpBackend.whenPOST("/api/sign_in",
          {
            user:
              email: "test@example.com"
              password: "password"
              password_confirmation: "12345678"
          }
        ).respond(422, {
            "status": 422,
            "messages": [
              "パスワードを再入力とパスワードの入力が一致しません。"
            ]
          })

        $httpBackend.when("GET", /.*/).passThrough()
      )
    )

  beforeEach ->
    browser.get("/sign_up")

  it "エラー表示確認", ->
   # test code here    

expect系が用意されていないのは、統合テストという性格上、実際にテスト用のAPIサーバを用意して実際の挙動でテストするのを推奨していて、モックは限定的に使えという意味合いなのかなと思った。

今回、APIサーバはRailsだが、RailsにAngularJSを載せるのではなく、AngularJSとRailsの役割を完全に切り離して開発しているので、E2Eテストでどうしようかなと模索中。 実際にrails serverをテスト環境で動かしてproxyでつなぐのがいいかなと思いつつ、DBデータのテスト事の処理をどうしようかと。 なにかいい方法を知っている方がいたら教えていただきたい感じ。