Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play! Scala 2.5 : testing classes injecting cache leads to an error

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...

like image 575
Simon Avatar asked Sep 12 '16 15:09

Simon


2 Answers

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.

like image 90
Simon Avatar answered Oct 10 '22 17:10

Simon


Having several tests building play application, the only way we found to solve this issue is :

  • to NOT use EhCache for the tests
  • to use a custom in memory AsyncCacheApi for the tests
  • to use EHCache for the production

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

like image 31
GeReinhart Avatar answered Oct 10 '22 16:10

GeReinhart