Error with Play 2.4 Tests : The CacheManager has been shut down. It can no longer be used

3.1k views Asked by At

Our application is built on Play 2.4 with Scala 2.11 and Akka. Database used is MySQL.

Cache is used heavily in our application.We use Play's default EhCache for caching.

Our sample code snippet :

import play.api.Play.current
import play.api.cache.Cache

case class Sample(var id: Option[String],
                 //.. other fields
)

class SampleTable(tag: Tag)
  extends Table[Sample](tag, "SAMPLE") {
  def id = column[Option[String]]("id", O.PrimaryKey)
  // .. other field defs
}

object SampleDAO extends TableQuery(new SampleTable(_)) with SQLWrapper {
  def get(id: String) : Future[Sample] = {
    val cacheKey = // our code to generate a unique cache key
    Cache.getOrElse[Future[[Sample]](cacheKey) {
      db.run(this.filter(_.id === id).result.headOption)
    }
  }
}

We use Play's inbuilt Specs2 for testing.

var id = "6879a389-aa3c-4074-9929-cca324c7a01f"

  "Sample Application " should {
    "Get a Sample" in new WithApplication {
      val req = FakeRequest(GET, s"/v1/samples/$id")
      val result = route(req).get
      assertEquals(OK, status(result))
      id = (contentAsJson(result).\("id")).get.toString().replaceAllLiterally("\"", "")
   }

But While unit-testing we often encounter the below error.

[error]    1) Error in custom provider, java.lang.IllegalStateException: The  CacheManager has been shut down. It can no longer b
e used.
[error]      at play.api.cache.EhCacheModule.play$api$cache$EhCacheModule$$bindCache$1(Cache.scala:181):
[error]    Binding(interface net.sf.ehcache.Ehcache qualified with QualifierInstance(@play.cache.NamedCache(value=play)) to Prov
iderTarget(play.api.cache.NamedEhCacheProvider@7c8b0968)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.ap
i.inject.guice.GuiceableModuleConversions$$anon$1)
[error]      while locating net.sf.ehcache.Ehcache annotated with @play.cache.NamedCache(value=play)
[error]      at play.api.cache.EhCacheModule.play$api$cache$EhCacheModule$$bindCache$1(Cache.scala:182):
[error]    Binding(interface play.api.cache.CacheApi qualified with QualifierInstance(@play.cache.NamedCache(value=play)) to Pro
viderTarget(play.api.cache.NamedCacheApiProvider@38514c74)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.
api.inject.guice.GuiceableModuleConversions$$anon$1)
[error]      while locating play.api.cache.CacheApi annotated with @play.cache.NamedCache(value=play)
[error]      while locating play.api.cache.CacheApi
[error]
[error]    1 error (InjectorImpl.java:1025)
[error] com.google.inject.internal.InjectorImpl$2.get(InjectorImpl.java:1025)
[error] com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
[error] play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:321)
[error] play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:316)
[error] play.api.Application$$anonfun$instanceCache$1.apply(Application.scala:234)
[error] play.api.Application$$anonfun$instanceCache$1.apply(Application.scala:234)
[error] play.utils.InlineCache.fresh(InlineCache.scala:69)
[error] play.utils.InlineCache.apply(InlineCache.scala:62)
[error] play.api.cache.Cache$.cacheApi(Cache.scala:63)
[error] play.api.cache.Cache$.getOrElse(Cache.scala:106

We look forward for help on either resolving the above issue or ways to implement a mock cache exclusively for Testing.

Thanks in Advance.

3

There are 3 answers

2
Steve Chaloner On BEST ANSWER

I had a similar issue, so I stubbed out a cache implementation and swapped it in for the tests.

class FakeCache extends CacheApi {
  override def set(key: String, value: Any, expiration: Duration): Unit = {}

  override def get[T](key: String)(implicit evidence$2: ClassManifest[T]): Option[T] = None

  override def getOrElse[A](key: String, expiration: Duration)(orElse: => A)(implicit evidence$1: ClassManifest[A]): A = orElse

  override def remove(key: String): Unit = {}
}

Override the injection:

class AbstractViewTest extends PlaySpecification {

  def testApp(handler: DeadboltHandler): Application = new GuiceApplicationBuilder()
                                                   .overrides(bind[CacheApi].to[FakeCache])
                                                   .in(Mode.Test)
                                                   .build()

}

You can see how I use this on GitHub: https://github.com/schaloner/deadbolt-2-scala/blob/master/code/test/be/objectify/deadbolt/scala/views/AbstractViewTest.scala

0
akkie On

Another solution would be to call the sequential method on the beginning of each test.

class MySpec extends Specification {
  sequential

  ...
}

Note

parallelExecution in Test := false

Should be set in the build.sbt file too.

0
Craig On

I think bypassing cache testing by faking a cache is a bad practice. It invalidates your tests, all because you are trying to not use EhCache in a scalable and distributed way. A much better approach is to implement a @Singleton interface as detailed here: https://stackoverflow.com/a/31835029/5736587

Thanks to https://stackoverflow.com/users/2145368/mon-calamari