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.
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
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With