Qiitaからの移植


はじめての投稿となります.
Scala.jsに関するTipsが(あるかもしれないけど)Qiita上で見つかりづらかったのでいくつかまとめました.分けるほどの内容も無いけど,検索時の利便性を鑑み分割で投稿します(3から4つ).

まずはScala.jsの試用中に,並列処理周りでハマったのでメモがてら書き残し

まとめ

  • Scala.jsの単体テストでScalaTestを利用する方法
  • Scala.jsの単体テストで,PromiseFutureを使う方法

テスト環境

ソフトウェア バージョン
Scala 2.11.7
Scala.js 0.6.6

基本事項

基本1: Scala.jsでのテスト

公式のチュートリアルでは,utestを使う事が推奨(?)されている.utestを利用する場合は,チュートリアルの通りになので省略する.ただ,

  1. utestを自分はよく知らない
  2. xUnitフレームワークっぽい記法でテスト名を書きづらい

等の理由から,使い慣れたScalaTestを使っている.なぜか何処にもドキュメントが無いが,公式のリリースノートを見る限り,3.0.0-M15からScala.jsが完全にサポートされたようだ.build設定も難しくなく,build.sbt

libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.0-M15" % "test"

と書くだけ.Scala.jsのバージョンに合わせるため,%%%を使うこと,指定するバージョンにさえ気をつければ普通のScalaTestの設定方法とほぼ変わらない. ちなみに,バージョンは3.0.0-M10でも今の所問題なく動いているので問題が起きればダウングレードも検討すべきかもしれない.

基本2: Scala.jsでの並列処理

JavaScriptといえば非同期API.当然,Scala.jsでも非同期APIを使うことはよくあり,scala.concurrent.{Promise|Future}を使う.使い方はここなどにある通りで,

import org.scalajs.dom
import org.scalajs.dom.window

val promise = Promise[Int]
window.onload = (e: dom.Event) => {
  promise.success(10)
}

promise.future foreach (result => println(result))

と書くと,ページ読み込みの終了後に”10”がコンソールへ出力される.このあたりは,普通のScalaと変わらない.

並列処理に関するテスト

非同期処理を使ったら,当然それのテストが書きたくなる.例えば,jQueryを利用して適当なWebページを持ってきて,その内容に対するテストを書くなら,

import org.scalatest._

import org.scalajs.dom
import org.scalajs.dom.window
import org.scalajs.dom.document
import org.scalajs.jquery.jQuery

import scala.concurrent.Promise
import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow

class ScalaTest extends WordSpec with Matchers {
  "jQuery" can {
    "get the content of HTML by using get function." in {
      val promise = Promise[dom.Event]

      jQuery.get(window.location.href, null, (e: dom.Event) => {
        promise.success(e)
      })

      for { event <- promise.future} {
        event should not be (null)
      }
    }
  }
}

みたいに書けそうに思える.しかし,このコードはassertionが呼ばれたり呼ばれなかったり動作が不安定になる.これは,

  1. ScalaTestのテストケースが呼ばれる.
  2. jQuery.getで,HTTPリクエストを飛ばす.
  3. 非同期APIのため,ScalaTestは次の処理に進み,終了する.
  4. (HTTPレスポンスが返ってくる. or sbt testが終了する)

となってしまい,assertionに処理が入らない.これだとテストにならないので,どうにかしないといけない.

解決方法

ScalaTest 3.0.0で,Async testing stylesが追加された.これを使うと,上記問題を解決できた.

import org.scalatest._

import org.scalajs.dom
import org.scalajs.dom.window
import org.scalajs.dom.document
import org.scalajs.jquery.jQuery

import scala.concurrent.Promise
import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow

class AsyncScalaTest extends AsyncWordSpec with Matchers {
  "jQuery" can {
    "get the content of HTML by using get function." in { {
      val promise = Promise[dom.Event]

      jQuery.get(window.location.href, null, (e: dom.Event) => {
        promise.success(e)
      })

      for { event <- promise.future} yield {
        event should not be (null)
      }
    }
  }
}

AsyncXXX(ここではAsyncWordSpec)は,Future[Assertion]をかえす必要がある.そのため,最後に,for yield文を使ってその中にassertionを書き,Futureを作ってあげるとコンパイルが通り,想定通りにテストが動く. ちなみに,公式ドキュメントで書かれているutestには,このような非同期処理のテストを行う方法は書いていない.別にScala.jsに特化したテスティングフレームワークではないみたいので,わざわざ学習する必要はないのでは無いのではと感じた.