玩! Scala 2.5:测试类注入缓存会导致错误

时间:2016-09-12 15:26:41

标签: scala caching playframework-2.5

我第一次使用Play的缓存! Scala 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)

但是当我测试类FacebookAPI的函数时,测试通过,但由于名称为'play'的EhCache实例已经存在,我仍然有很多错误消息。

5 个答案:

答案 0 :(得分:3)

我终于找到了解决方案。

我在test.conf文件中添加了(在conf文件夹中):

play.cache.bindCaches = ["controller-cache", "document-cache"]

play.cache.createBoundCaches = false

为了使测试中使用的conf文件我刚刚在build.sbt的设置部分添加了以下行:

javaOptions in Test += "-Dconfig.resource=tests.conf"

如果您需要更多详细信息,请与我们联系。

答案 1 :(得分:3)

拥有多个构建游戏应用程序的测试,我们发现解决此问题的唯一方法是:

  • 不使用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)

使用的版本:播放2.6.15和Scala 2.12.4

答案 2 :(得分:2)

在我的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
  }

}

在下面需要导入的应用程序构建器中

import play.api.inject.bind

答案 3 :(得分:0)

这很可能是由于测试的并行性质。例如,我正在使用specs2并且如果我有两个使用“WithApplication()”的测试(假冒应用程序在2.5.x中),我将收到有关ehcache的错误。

我的解决方案是在另一个之后运行测试。在specs2的情况下,只需在测试类的开头添加“顺序”。我不确定如何在“ScalaTestPlus”中做到这一点,但你明白了。

答案 4 :(得分:0)

如果您有多个使用同一缓存的测试套件,则可以在每个测试套件的末尾显式关闭CacheManager并依次运行它们:

override def afterAll(): Unit = {
  CacheManager.getInstance().shutdown()
}