Posted at

Scala.jsのテストでPromise,Futureを使う (ScalaTest)

More than 3 years have passed since last update.

はじめての投稿となります.


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に特化したテスティングフレームワークではないみたいので,わざわざ学習する必要はないのでは無いのではと感じた.