Scala.jsのテストでPromise,Futureを使う (ScalaTest)
Qiitaからの移植
はじめての投稿となります.
Scala.jsに関するTipsが(あるかもしれないけど)Qiita上で見つかりづらかったのでいくつかまとめました.分けるほどの内容も無いけど,検索時の利便性を鑑み分割で投稿します(3から4つ).
まずはScala.jsの試用中に,並列処理周りでハマったのでメモがてら書き残し
まとめ
- Scala.jsの単体テストでScalaTestを利用する方法
- Scala.jsの単体テストで,
Promise
,Future
を使う方法
テスト環境
ソフトウェア | バージョン |
---|---|
Scala | 2.11.7 |
Scala.js | 0.6.6 |
基本事項
基本1: Scala.jsでのテスト
公式のチュートリアルでは,utestを使う事が推奨(?)されている.utestを利用する場合は,チュートリアルの通りになので省略する.ただ,
- utestを自分はよく知らない
- 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が呼ばれたり呼ばれなかったり動作が不安定になる.これは,
- ScalaTestのテストケースが呼ばれる.
jQuery.get
で,HTTPリクエストを飛ばす.- 非同期APIのため,ScalaTestは次の処理に進み,終了する.
- (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に特化したテスティングフレームワークではないみたいので,わざわざ学習する必要はないのでは無いのではと感じた.