玩! Scala 2.5:测试 类 注入缓存导致错误
Play! Scala 2.5 : testing classes injecting cache leads to an error
我是第一次使用Play的缓存!斯卡拉 2.5。除了测试,它工作得很好。
我的测试仍然通过,因为我不需要缓存,但我得到了这个错误(还有很多其他人告诉同样的事情):
Unable to provision, see the following errors:
1) Error in custom provider, play.api.cache.EhCacheExistsException: An EhCache instance with name 'play' already exists.
我明白这个错误,但我没有设法实现我自己的缓存版本 API(模拟它)。
我尝试按照 play mailing list 上的说明进行操作,但没有成功(与 Play! 2.4 存在一些差异,因为该模块是依赖注入的)。欢迎任何帮助。
编辑: 我所做的(并没有改变任何东西):
我的 CacheApi 版本(仅用于测试):
class MyCacheApi extends CacheApi {
lazy val cache = {
val manager = CacheManager.getInstance()
manager.addCacheIfAbsent("play")
manager.getCache("play")
}
def set(key: String, value: Any, expiration: Duration = Duration.Inf) = {}
def remove(key: String) = {}
def getOrElse[A: ClassTag](key: String, expiration: Duration = Duration.Inf)(orElse: => A): A = {
get[A](key).getOrElse {
val value = orElse
set(key, value, expiration)
value
}
}
def get[T: ClassTag](key: String): Option[T] = None
}
在我的测试中,我是这样使用它的:
lazy val appBuilder = new GuiceApplicationBuilder()
.in(Mode.Test)
.overrides(bind[CacheApi].to[MyCacheApi])
lazy val injector = appBuilder.injector()
lazy val cache = new MyCacheApi
lazy val facebookAPI = new FacebookAPI(cache)
但是当我测试 class FacebookAPI
的功能时,测试通过了,但是由于名称为 'play' 已经存在...
这很可能是由于测试的并行性质。例如,我正在使用 specs2 并且如果我有两个使用 "WithApplication()" 的测试(在 play 2 中伪造一个应用程序。5.x)我将得到关于 ehcache 的错误。
我的解决方案是运行一个接一个地测试。在 specs2 的情况下,只需在测试 class 的开头添加 "sequential"。我不确定如何在 "ScalaTestPlus" 中做到这一点,但你明白了。
我终于找到了解决办法。
我在 test.conf 文件中添加(在 conf 文件夹中):
play.cache.bindCaches = ["controller-cache", "document-cache"]
play.cache.createBoundCaches = false
为了让这个 conf 文件在测试中使用,我刚刚在 build.sbt 的设置部分添加了以下行:
javaOptions in Test += "-Dconfig.resource=tests.conf"
如果您需要更多详细信息,请告诉我。
对构建播放应用程序进行了多次测试,我们发现解决此问题的唯一方法是:
- 到 不 使用 EhCache 进行测试
- 使用内存中的自定义 AsyncCacheApi 进行测试
- 将 EHCache 用于生产
因此在用于测试的 application.conf 中,并且仅用于测试,禁用默认缓存:
play.modules.disabled += "play.api.cache.ehcache.EhCacheModule"
使用扩展 AsyncCacheApi 的映射编写缓存的实现:
package utils
import akka.Done
import net.sf.ehcache.Element
import play.api.cache.AsyncCacheApi
import scala.concurrent.duration.Duration
import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.ClassTag
class InMemoryCache (implicit ec : ExecutionContext) extends AsyncCacheApi {
val cache = scala.collection.mutable.Map[String, Element]()
def set(key: String, value: Any, expiration: Duration): Future[Done] = Future {
val element = new Element(key, value)
if (expiration == 0) element.setEternal(true)
element.setTimeToLive(expiration.toSeconds.toInt)
cache.put(key, element)
Done
}
def remove(key: String): Future[Done] = Future {
cache -= key
Done
}
def get[T: ClassTag](key: String): Future[Option[T]] = Future {
cache.get(key).map(_.getObjectValue).asInstanceOf[Option[T]]
}
def getOrElseUpdate[A: ClassTag](key: String, expiration: Duration)(orElse: => Future[A]): Future[A] = {
get[A](key).flatMap {
case Some(value) => Future.successful(value)
case None => orElse.flatMap(value => set(key, value, expiration).map(_ => value))
}
}
def removeAll(): Future[Done] = Future {
cache.clear()
Done
}
}
然后进行测试:
val application = new GuiceApplicationBuilder().
overrides(
bind[AsyncCacheApi].toInstance(new utils.InMemoryCache())
).build
Play.start(application)
使用的版本:Play 2.6.15 和 Scala 2.12.4
在我的 play(2.6.17) 和 scala (2.12.6) 版本中,@GeReinhart 的回答需要小的改动。
一个注入注解
expiration.toSeconds 如果到期是无限的,则抛出非法参数异常,因此有限检查
-
import akka.Done
import javax.inject.Inject
import net.sf.ehcache.Element
import play.api.cache.AsyncCacheApi
import scala.concurrent.duration.Duration
import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.ClassTag
class InMemoryCache @Inject()()(implicit ec: ExecutionContext) extends AsyncCacheApi {
val cache = scala.collection.mutable.Map[String, Element]()
def remove(key: String): Future[Done] = Future {
cache -= key
Done
}
def getOrElseUpdate[A: ClassTag](key: String, expiration: Duration)(orElse: => Future[A]): Future[A] = {
get[A](key).flatMap {
case Some(value) => Future.successful(value)
case None => orElse.flatMap(value => set(key, value, expiration).map(_ => value))
}
}
def set(key: String, value: Any, expiration: Duration): Future[Done] = Future {
val element = new Element(key, value)
if (expiration.isFinite()) {
element.setTimeToLive(expiration.toSeconds.toInt)
} else {
element.setEternal(true)
}
cache.put(key, element)
Done
}
def get[T: ClassTag](key: String): Future[Option[T]] = Future {
cache.get(key).map(_.getObjectValue).asInstanceOf[Option[T]]
}
def removeAll(): Future[Done] = Future {
cache.clear()
Done
}
}
在下方需要导入的application builder中
import play.api.inject.bind
如果您有多个测试套件使用相同的缓存,您可以在每个测试套件结束时显式关闭 CacheManager 并 运行 它们按顺序关闭:
override def afterAll(): Unit = {
CacheManager.getInstance().shutdown()
}
我是第一次使用Play的缓存!斯卡拉 2.5。除了测试,它工作得很好。
我的测试仍然通过,因为我不需要缓存,但我得到了这个错误(还有很多其他人告诉同样的事情):
Unable to provision, see the following errors:
1) Error in custom provider, play.api.cache.EhCacheExistsException: An EhCache instance with name 'play' already exists.
我明白这个错误,但我没有设法实现我自己的缓存版本 API(模拟它)。
我尝试按照 play mailing list 上的说明进行操作,但没有成功(与 Play! 2.4 存在一些差异,因为该模块是依赖注入的)。欢迎任何帮助。
编辑: 我所做的(并没有改变任何东西):
我的 CacheApi 版本(仅用于测试):
class MyCacheApi extends CacheApi {
lazy val cache = {
val manager = CacheManager.getInstance()
manager.addCacheIfAbsent("play")
manager.getCache("play")
}
def set(key: String, value: Any, expiration: Duration = Duration.Inf) = {}
def remove(key: String) = {}
def getOrElse[A: ClassTag](key: String, expiration: Duration = Duration.Inf)(orElse: => A): A = {
get[A](key).getOrElse {
val value = orElse
set(key, value, expiration)
value
}
}
def get[T: ClassTag](key: String): Option[T] = None
}
在我的测试中,我是这样使用它的:
lazy val appBuilder = new GuiceApplicationBuilder()
.in(Mode.Test)
.overrides(bind[CacheApi].to[MyCacheApi])
lazy val injector = appBuilder.injector()
lazy val cache = new MyCacheApi
lazy val facebookAPI = new FacebookAPI(cache)
但是当我测试 class FacebookAPI
的功能时,测试通过了,但是由于名称为 'play' 已经存在...
这很可能是由于测试的并行性质。例如,我正在使用 specs2 并且如果我有两个使用 "WithApplication()" 的测试(在 play 2 中伪造一个应用程序。5.x)我将得到关于 ehcache 的错误。
我的解决方案是运行一个接一个地测试。在 specs2 的情况下,只需在测试 class 的开头添加 "sequential"。我不确定如何在 "ScalaTestPlus" 中做到这一点,但你明白了。
我终于找到了解决办法。
我在 test.conf 文件中添加(在 conf 文件夹中):
play.cache.bindCaches = ["controller-cache", "document-cache"]
play.cache.createBoundCaches = false
为了让这个 conf 文件在测试中使用,我刚刚在 build.sbt 的设置部分添加了以下行:
javaOptions in Test += "-Dconfig.resource=tests.conf"
如果您需要更多详细信息,请告诉我。
对构建播放应用程序进行了多次测试,我们发现解决此问题的唯一方法是:
- 到 不 使用 EhCache 进行测试
- 使用内存中的自定义 AsyncCacheApi 进行测试
- 将 EHCache 用于生产
因此在用于测试的 application.conf 中,并且仅用于测试,禁用默认缓存:
play.modules.disabled += "play.api.cache.ehcache.EhCacheModule"
使用扩展 AsyncCacheApi 的映射编写缓存的实现:
package utils
import akka.Done
import net.sf.ehcache.Element
import play.api.cache.AsyncCacheApi
import scala.concurrent.duration.Duration
import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.ClassTag
class InMemoryCache (implicit ec : ExecutionContext) extends AsyncCacheApi {
val cache = scala.collection.mutable.Map[String, Element]()
def set(key: String, value: Any, expiration: Duration): Future[Done] = Future {
val element = new Element(key, value)
if (expiration == 0) element.setEternal(true)
element.setTimeToLive(expiration.toSeconds.toInt)
cache.put(key, element)
Done
}
def remove(key: String): Future[Done] = Future {
cache -= key
Done
}
def get[T: ClassTag](key: String): Future[Option[T]] = Future {
cache.get(key).map(_.getObjectValue).asInstanceOf[Option[T]]
}
def getOrElseUpdate[A: ClassTag](key: String, expiration: Duration)(orElse: => Future[A]): Future[A] = {
get[A](key).flatMap {
case Some(value) => Future.successful(value)
case None => orElse.flatMap(value => set(key, value, expiration).map(_ => value))
}
}
def removeAll(): Future[Done] = Future {
cache.clear()
Done
}
}
然后进行测试:
val application = new GuiceApplicationBuilder().
overrides(
bind[AsyncCacheApi].toInstance(new utils.InMemoryCache())
).build
Play.start(application)
使用的版本:Play 2.6.15 和 Scala 2.12.4
在我的 play(2.6.17) 和 scala (2.12.6) 版本中,@GeReinhart 的回答需要小的改动。
一个注入注解
expiration.toSeconds 如果到期是无限的,则抛出非法参数异常,因此有限检查
-
import akka.Done
import javax.inject.Inject
import net.sf.ehcache.Element
import play.api.cache.AsyncCacheApi
import scala.concurrent.duration.Duration
import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.ClassTag
class InMemoryCache @Inject()()(implicit ec: ExecutionContext) extends AsyncCacheApi {
val cache = scala.collection.mutable.Map[String, Element]()
def remove(key: String): Future[Done] = Future {
cache -= key
Done
}
def getOrElseUpdate[A: ClassTag](key: String, expiration: Duration)(orElse: => Future[A]): Future[A] = {
get[A](key).flatMap {
case Some(value) => Future.successful(value)
case None => orElse.flatMap(value => set(key, value, expiration).map(_ => value))
}
}
def set(key: String, value: Any, expiration: Duration): Future[Done] = Future {
val element = new Element(key, value)
if (expiration.isFinite()) {
element.setTimeToLive(expiration.toSeconds.toInt)
} else {
element.setEternal(true)
}
cache.put(key, element)
Done
}
def get[T: ClassTag](key: String): Future[Option[T]] = Future {
cache.get(key).map(_.getObjectValue).asInstanceOf[Option[T]]
}
def removeAll(): Future[Done] = Future {
cache.clear()
Done
}
}
在下方需要导入的application builder中
import play.api.inject.bind
如果您有多个测试套件使用相同的缓存,您可以在每个测试套件结束时显式关闭 CacheManager 并 运行 它们按顺序关闭:
override def afterAll(): Unit = {
CacheManager.getInstance().shutdown()
}