Reputation: 6462
I'm using for the first time the cache of Play! Scala 2.5. It works well except for the tests.
My test still pass since I don't need the cache but I get this error (and a lot of others telling the same thing):
Unable to provision, see the following errors:
1) Error in custom provider, play.api.cache.EhCacheExistsException: An EhCache instance with name 'play' already exists.
I understand the error but I didn't manage to implement my own version of the cache API (to mock it).
I tried to do what is told on the play mailing list but without success (there is some differences with Play! 2.4 since the module is dependency injected). Any help would be welcome.
Edit: what I have done (and it does not change anything):
My CacheApi version (just for the tests):
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
}
And in my tests, I use it like this:
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)
But when I'm testing the functions of the class FacebookAPI
, the tests pass, but I still have a lot of error messages due to the fact that an EhCache instance with name 'play' already exists...
Upvotes: 8
Views: 4234
Reputation: 1482
If you have more than one test suite that uses the same cache, you can explicitly shutdown the CacheManager at the end of each test suite and run them sequentially:
override def afterAll(): Unit = {
CacheManager.getInstance().shutdown()
}
Upvotes: 2
Reputation: 320
In my version of play(2.6.17) and scala (2.12.6) @GeReinhart 's answer needs small changes.
An Inject annotation
expiration.toSeconds throws illegal argument exception if expiration is infinite, so a finite check
-
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
}
}
In the application builder that needs import below
import play.api.inject.bind
Upvotes: 2
Reputation: 423
Having several tests building play application, the only way we found to solve this issue is :
So in the application.conf used for the tests, and only for tests, disable the default caching :
play.modules.disabled += "play.api.cache.ehcache.EhCacheModule"
Write an implementation of the cache with a map that extends 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
}
}
Then on your tests :
val application = new GuiceApplicationBuilder().
overrides(
bind[AsyncCacheApi].toInstance(new utils.InMemoryCache())
).build
Play.start(application)
Versions used : Play 2.6.15 & Scala 2.12.4
Upvotes: 3
Reputation: 6462
I finally found a solution.
I added in a test.conf file (in the conf folder):
play.cache.bindCaches = ["controller-cache", "document-cache"]
play.cache.createBoundCaches = false
And to make this conf file used in the test I just added in the setting part of my build.sbt the following line:
javaOptions in Test += "-Dconfig.resource=tests.conf"
Let me know if you need more details.
Upvotes: 6
Reputation: 3709
This is most likely due to the parallel nature of the tests. For instance, I'm using specs2 and If I have two tests that use "WithApplication()" (to fake an application in play 2.5.x) I will get the error about ehcache.
My solution was to run a test after the other. In the case of specs2 just by adding "sequential" in the beginning of the test class. I'm not sure how to do that in "ScalaTestPlus" but you get the point.
Upvotes: 0