I'm using Scala Play 2.7.x (the project is available here Play-Silhouette-Seed) and would like to test my daos. I have put together this simple one first to check what's the "new pattern" for testing play + slick + guice in 2.7.x:
package models.daos
import java.util.UUID
import org.specs2.mock._
import org.specs2.mutable._
import utils.AwaitUtil
import javax.inject.Inject
import models.generated.Tables.LoginInfoRow
class LoginInfoDaoSpec #Inject() (loginInfoDao: LoginInfoDao) extends Specification with Mockito with AwaitUtil {
"Creating a new LoginInfo" should {
"save it in the empty database" in {
loginInfoDao.create(LoginInfoRow(0, UUID.randomUUID().toString, UUID.randomUUID().toString))
loginInfoDao.findAll.size should beEqualTo(1)
}
}
}
Unfortunately, the guice dependency LoginInfoDao is not being provided to my test and then I get the error:
[play-silhouette-seed] $ testOnly models.daos.LoginInfoDaoSpec
[info] Done compiling.
[error] Can't find a suitable constructor with 0 or 1 parameter for class models.daos.LoginInfoDao
[info] ScalaTest
[info] Run completed in 1 second, 966 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 0
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] No tests were executed.
[error] Error: Total 1, Failed 0, Errors 1, Passed 0
How do I get guice loading the needed modules for my test-cases?
A module is defined as:
class SilhouetteModule extends AbstractModule with ScalaModule {
override def configure() {
// ...
bind[LoginInfoDao].to[LoginInfoDaoImpl]
// ...
}
}
and I have an application.test.conf available defined as:
include "application.conf"
slick.dbs {
default {
profile="slick.jdbc.MySQLProfile$"
db.driver="com.mysql.cj.jdbc.Driver"
db.url="jdbc:mysql://localhost:3306/mytestdb?useUnicode=true&searchpath=public&serverTimezone=CET"
db.user="dev"
db.password="12345"
}
}
You should use another database for testing, H2 is the common choice.
class Module extends AbstractModule with ScalaModule {
...
}
With that name you need to let play know there is a Module to load, if you call it Module, it automatically know what to load and should work just fine (if you only need 1 module in testing)
If that doesn't work let me know
I am trying to use internationalization in twirl templates. I followed this guide: https://stackoverflow.com/a/30800825/1567737
I am having issues with the implicit messages: Messages. I have reduced my setup to the bare minimum on which the error occurs:
TestController
import javax.inject.Inject
import play.api.i18n.{I18nSupport, MessagesApi}
import play.api.mvc.{Action, Controller}
class TestController #Inject()(val messagesApi: MessagesApi)
extends Controller with I18nSupport {
def index = Action {
Ok(views.html.test.render())
}
}
test.scala.html
#()(implicit messages: Messages)
I have also added routesGenerator := InjectedRoutesGenerator to my build.sbt.
This should be sufficient according to the docs and the guide I linked above. Still I get the following compile time error:
[error] app/controllers/TestController.scala:11: not enough arguments for method render: (messages: play.api.i18n.Messages)play.twirl.api.HtmlFormat.Appendable.
[error] Unspecified value parameter messages.
[error] Ok(views.html.test.render())
Play Version: 2.5.9
SBT version: 0.13.11
Scala version: 2.11.7
Well, apparently implicit parameters only work on the apply method. And not when you explicitly call render.
Ok(views.html.test.render())
Must be
Ok(views.html.test())
Play 2.4 app, using dependency injection for service classes.
I found that Specs2 chokes when a service class being tested has more than one injected dependency. It fails with "Can't find a constructor for class ..."
$ test-only services.ReportServiceSpec
[error] Can't find a constructor for class services.ReportService
[error] Error: Total 1, Failed 0, Errors 1, Passed 0
[error] Error during tests:
[error] services.ReportServiceSpec
[error] (test:testOnly) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 2 s, completed Dec 8, 2015 5:24:34 PM
Production code, stripped to bare minimum to reproduce this problem:
package services
import javax.inject.Inject
class ReportService #Inject()(userService: UserService, supportService: SupportService) {
// ...
}
class UserService {
// ...
}
class SupportService {
// ...
}
Test code:
package services
import javax.inject.Inject
import org.specs2.mutable.Specification
class ReportServiceSpec #Inject()(service: ReportService) extends Specification {
"ReportService" should {
"Work" in {
1 mustEqual 1
}
}
}
If I remove either UserService or SupportService dependency from ReportService, the test works. But obviously the dependencies are in the production code for a reason. Question is, how do I make this test work?
Edit: When trying to run the test inside IntelliJ IDEA, the same thing fails, but with different messages: "Test framework quit unexpectedly", "This looks like a specs2 exception..."; see full output with stacktrace. I opened a Specs2 issue as instructed in the output, though I have no idea if the problem is in Play or Specs2 or somewhere else.
My library dependencies below. (I tried specifying Specs2 version explicitly, but that didn't help. Looks like I need specs2 % Test as is, for Play's test classes like WithApplication to work.)
resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases"
libraryDependencies ++= Seq(
specs2 % Test,
jdbc,
evolutions,
filters,
"com.typesafe.play" %% "anorm" % "2.4.0",
"org.postgresql" % "postgresql" % "9.4-1205-jdbc42"
)
There is limited support for dependency injection in specs2, mostly for execution environments or command-line arguments.
There is nothing preventing you from just using a lazy val and your favourite injection framework:
class MySpec extends Specification with Inject {
lazy val reportService = inject[ReportService]
...
}
With Play and Guice, you could have a test helper such as this:
import play.api.inject.guice.GuiceApplicationBuilder
import scala.reflect.ClassTag
trait Inject {
lazy val injector = (new GuiceApplicationBuilder).injector()
def inject[T : ClassTag]: T = injector.instanceOf[T]
}
If you really need runtime dependency injection, then it's better to use Guice loading, I guess:
package services
import org.specs2.mutable.Specification
import scala.reflect.ClassTag
import com.google.inject.Guice
// Something you'd like to share between your tests
// or maybe not
object Inject {
lazy val injector = Guice.createInjector()
def apply[T <: AnyRef](implicit m: ClassTag[T]): T =
injector.getInstance(m.runtimeClass).asInstanceOf[T]
}
class ReportServiceSpec extends Specification {
lazy val reportService: ReportService = Inject[ReportService]
"ReportService" should {
"Work" in {
reportService.foo mustEqual 2
}
}
}
Alternatively you can implement Inject object as
import scala.reflect.ClassTag
import play.api.inject.guice.GuiceApplicationBuilder
object Inject {
lazy val injector = (new GuiceApplicationBuilder).injector()
def apply[T : ClassTag]: T = injector.instanceOf[T]
}
It depends whether you want to use Guice directly, or thru play wrappers.
Looks like you are out of luck ATM: The comment says
Try to create an instance of a given class by using whatever constructor is available and trying to instantiate the first parameter recursively if there is a parameter for that constructor.
val constructors = klass.getDeclaredConstructors.toList.filter(_.getParameterTypes.size <= 1).sortBy(_.getParameterTypes.size)
i.e. Specs2 doesn't provide own DI out-of-the box,
Or you can reimplement the functionality yourself, if Guice isn't working for you.
App code:
package services
import javax.inject.Inject
class ReportService #Inject()(userService: UserService, supportService: SupportService) {
val foo: Int = userService.foo + supportService.foo
}
class UserService {
val foo: Int = 1
}
class SupportService {
val foo: Int = 41
}
Test code
package services
import org.specs2.mutable.Specification
import scala.reflect.ClassTag
import java.lang.reflect.Constructor
class Trick {
val m: ClassTag[ReportService] = implicitly
val classLoader: ClassLoader = m.runtimeClass.getClassLoader
val trick: ReportService = Trick.createInstance[ReportService](m.runtimeClass, classLoader)
}
object Trick {
def createInstance[T <: AnyRef](klass: Class[_], loader: ClassLoader)(implicit m: ClassTag[T]): T = {
val constructors = klass.getDeclaredConstructors.toList.sortBy(_.getParameterTypes.size)
val constructor = constructors.head
createInstanceForConstructor(klass, constructor, loader)
}
private def createInstanceForConstructor[T <: AnyRef : ClassTag]
(c: Class[_], constructor: Constructor[_], loader: ClassLoader): T = {
constructor.setAccessible(true)
// This can be implemented generically, but I don't remember how to deal with variadic functions
// generically. IIRC even more reflection.
if (constructor.getParameterTypes.isEmpty)
constructor.newInstance().asInstanceOf[T]
else if (constructor.getParameterTypes.size == 1) {
// not implemented
null.asInstanceOf[T]
} else if (constructor.getParameterTypes.size == 2) {
val types = constructor.getParameterTypes.toSeq
val param1 = createInstance(types(0), loader)
val param2 = createInstance(types(1), loader)
constructor.newInstance(param1, param2).asInstanceOf[T]
} else {
// not implemented
null.asInstanceOf[T]
}
}
}
// NB: no need to #Inject here. The specs2 framework does it for us.
// It sees spec with parameter, and loads it for us.
class ReportServiceSpec (trick: Trick) extends Specification {
"ReportService" should {
"Work" in {
trick.trick.foo mustEqual 2
}
}
}
And that expectedly fails with
[info] ReportService should
[error] x Work
[error] '42' is not equal to '2' (FooSpec.scala:46)
If you don't need runtime dependency injection, then it's better to use cake pattern, and forget reflection all-together.
My colleague suggested a "low-tech" workaround. In the test, instantiate service classes with new:
class ReportServiceSpec extends Specification {
val service = new ReportService(new UserService, new SupportService)
// ...
}
This also works:
class ReportServiceSpec #Inject()(userService: UserService) extends Specification {
val service = new ReportService(userService, new SupportService)
// ...
}
Feel free to post more elegant solutions. I've yet to see a simple DI solution that works (with Guice, Play's default).
Does anyone else find it curious that Play's default test framework does not play well with Play's default DI mechanism?
Edit: In the end I went with an "Injector" test helper, almost the same as what Eric suggested:
Injector:
package testhelpers
import play.api.inject.guice.GuiceApplicationBuilder
import scala.reflect.ClassTag
/**
* Provides dependency injection for test classes.
*/
object Injector {
lazy val injector = (new GuiceApplicationBuilder).injector()
def inject[T: ClassTag]: T = injector.instanceOf[T]
}
Test:
class ReportServiceSpec extends Specification {
val service = Injector.inject[ReportService]
// ...
}
Play is working fine when I am using sbt run. However, when I am trying to do such test:
#RunWith(classOf[JUnitRunner])
class MarketApiSpec extends Specification {
"Market API" should {
"load product list" in new WithApplication {
val completed = route(FakeRequest("GET", "/api/products")).get.map(x => Json.parse(x.body.toString()))
val jsonObject = Await.result(completed, Duration("1s"))
jsonObject.get("products").get(0).get("name").asText() must beEqualTo("Programmer");
}
}
}
By a sbt test I get such error exception:
[info] Market API should
[info] ! load product list
[error] RuntimeException: : java.lang.NoClassDefFoundError: Could not initialize class controllers.WebJarAssets$ (Action.scala:523)
[error] play.api.mvc.ActionBuilder$$anon$1.apply(Action.scala:523)
I googled a lot, however I didn't find the solution. I think its related to sbt somehow, but even giving a test scope doesn't help.
I use Scala, Play 2.4, and Slick 3 in my project. I have following DAO code and it works fine from end to end.
#Singleton()
class CompaniesDAO #Inject() (protected val dbConfigProvider: DatabaseConfigProvider) extends CompaniesComponent
with HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
}
However, I can't get it working as expected in my integration test because of dbConfig stuff. My integration test is below:
class CompaniesDaoIntegrationSpec extends FunSpec with OneServerPerSuite {
def companiesDao(implicit app: Application) = {
val app2CompaniesDAO = Application.instanceCache[CompaniesDAO]
app2CompaniesDAO(app)
}
describe("create") {
it("should create ") {
companiesDao.create...
}
}
}
If I don't put db properties in application.conf I got the following error:
[info] java.lang.RuntimeException: com.google.inject.ProvisionException: Unable to provision, see the following errors:
[info]
[info] 1) No implementation for play.api.db.slick.DatabaseConfigProvider was bound.
[info] while locating play.api.db.slick.DatabaseConfigProvider
[info] for parameter 0 at
It seems given the above code, Play application reads the db properties from configuration file which is located at /conf/application.conf.
My project setup is a bit different to this, as we have multiple environments so we have file structuers like:
/conf/local/application.conf
/conf/testing/application.conf
/conf/staging/application.conf
/conf/production/application.conf
When we run the play application using command like: activator run -Dconfig.resource=/conf/local/application.conf and everything work fine.
I want to do the same for integration spec like: activator test -Dconfig.resource=/conf/local/application.conf.
Play will read the specified config to run integration tests.
What's the best way to achieve that?
You have to make a trait and mix it in the test, and then it will work.
trait WithDatabaseConfig {
lazy val (driver, db) = {
val dbConfig = DatabaseConfigProvider.get[JdbcProfile](Play.current)
(dbConfig.driver, dbConfig.db)
}
}
I have no idea why, I'm a Scala beginner. Probably has to do something with not/running app or Guice. Found it in their samples folder at https://github.com/playframework/play-slick/blob/1.1.x/samples/json/test/DBSpec.scala