Play scala integration spec - Injecting dependencies through Guice - scala

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

Related

mill client.fastOpt: client.scalaJSLinkerClasspath scala.MatchError: 1 (of class java.lang.String)

I want to run a ScalaJS module with mill Build Tool.
When running mill client.fastOpt I get:
[6/73] client.scalaJSLinkerClasspath
1 targets failed
client.scalaJSLinkerClasspath scala.MatchError: 1 (of class java.lang.String)
mill.scalajslib.ScalaJSModule.$anonfun$scalaJSLinkerClasspath$2(ScalaJSModule.scala:38)
mill.define.ApplyerGenerated.$anonfun$zipMap$7(ApplicativeGenerated.scala:17)
mill.define.Task$MappedDest.evaluate(Task.scala:365)
My build.sc is:
trait BaseJsModule extends ScalaJSModule {
val scalaJSVersion = "1.0.1"
val scalaVersion = "2.13.1"
}
object client extends BaseJsModule {
override def moduleDeps = Seq(shared)
override def mainClass = Some("pme123.camunda.boot.client.HelloClient")
}
object shared extends BaseJsModule
Do I miss something?
Your posted buildfile looks ok. You're probably using a too old mill version? Support for ScalaJS 1.0.0+ was added in mill 0.6.1.
Please note, that you can create a file .mill-version with the content 0.6.1 to automatically download and use mill 0.6.1.

Scala Play + Slick: How to inject dependencies to Spec tests?

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

How SBT test task manages class path and how to correctly start a Java process from SBT test

In one of my Scala tests, using ProcessBuilder, I fire up 3 Apache Spark streaming applications in separate JVMs. (Two or more Spark streaming applications can not co-exist in the same JVM.) One Spark application processes data and ingests into Apache Kafka, which the other ones read. Moreover the test involves writing into a NoSQL database.
While using ProcessBuilder, the Spark application's class path is set using:
val classPath = System.getProperty("java.class.path")
Running the test in IntelliJ works as expected, but on a CI system, the test is invoked by SBT's test task. The java.class.path in the latter case, will be solely the sbt.jar, so the child JVM exits with NoClassFoundException, again, as expected. :-)
I'm looking for a way to "span" JVMs from SBT tests using the same class path that the tests are actually using. For example if the test is invoked in project core, the class path of project core should be supplied to the child JVM, where the Spark application starts. Unfortunately I have got no idea how to retrieve the correct class path in SBT tasks - which then could be supplied to child JVMs.
Tests.Setup can be used to access the classpath within SBT:
testOptions in Test += Tests.Setup { classLoader =>
// give Spark classpath via classLoader
}
For example, on my machine Tests.Setup(classLoader => println(classLoader)) gives
> test
ClasspathFilter(
parent = URLClassLoader with NativeCopyLoader with RawResources(
urls = List(/home/mario/sandbox/sbt/so-classpath/target/scala-2.12/test-classes, /home/mario/sandbox/sbt/so-classpath/target/scala-2.12/classes, /home/mario/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.12.4.jar, /home/mario/.ivy2/cache/org.scalatest/scalatest_2.12/bundles/scalatest_2.12-3.0.5.jar, /home/mario/.ivy2/cache/org.scalactic/scalactic_2.12/bundles/scalactic_2.12-3.0.5.jar, /home/mario/.ivy2/cache/org.scala-lang/scala-reflect/jars/scala-reflect-2.12.4.jar, /home/mario/.ivy2/cache/org.scala-lang.modules/scala-xml_2.12/bundles/scala-xml_2.12-1.0.6.jar),
parent = DualLoader(a = java.net.URLClassLoader#3fcb37f1, b = java.net.URLClassLoader#271053e1),
resourceMap = Set(app.class.path, boot.class.path),
nativeTemp = /tmp/sbt_741bc913/sbt_c770779a
)
root = sun.misc.Launcher$AppClassLoader#33909752
cp = Set(/home/mario/.ivy2/cache/jline/jline/jars/jline-2.14.5.jar, /home/mario/.ivy2/cache/org.scala-lang/scala-reflect/jars/scala-reflect-2.12.4.jar, /home/mario/.ivy2/cache/org.scala-lang/scala-compiler/jars/scala-compiler-2.12.4.jar, /home/mario/.ivy2/cache/org.scala-lang.modules/scala-xml_2.12/bundles/scala-xml_2.12-1.0.6.jar, /home/mario/sandbox/sbt/so-classpath/target/scala-2.12/classes, /home/mario/.ivy2/cache/org.scalatest/scalatest_2.12/bundles/scalatest_2.12-3.0.5.jar, /home/mario/.sbt/boot/scala-2.10.7/org.scala-sbt/sbt/0.13.17/test-interface-1.0.jar, /home/mario/.ivy2/cache/org.scalactic/scalactic_2.12/bundles/scalactic_2.12-3.0.5.jar, /home/mario/sandbox/sbt/so-classpath/target/scala-2.12/test-classes, /home/mario/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.12.4.jar)
)
where we see that
.../target/scala-2.12/test-classes
.../target/scala-2.12/classes
are present.
On the other hand, to retrieve the classpath from within the test itself:
val classLoader = this.getClass.getClassLoader
// give Spark classpath via classLoader
For example, on my machine, println(classLoader) given in the following test
class CubeCalculatorTest extends FunSuite {
test("CubeCalculator.cube") {
val classLoader = this.getClass.getClassLoader
println(classLoader)
assert(CubeCalculator.cube(3) === 27)
}
}
prints
URLClassLoader with NativeCopyLoader with RawResources(
urls = List(/home/mario/sandbox/sbt/so-classpath/target/scala-2.12/test-classes, /home/mario/sandbox/sbt/so-classpath/target/scala-2.12/classes, /home/mario/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.12.4.jar, /home/mario/.ivy2/cache/org.scalatest/scalatest_2.12/bundles/scalatest_2.12-3.0.5.jar, /home/mario/.ivy2/cache/org.scalactic/scalactic_2.12/bundles/scalactic_2.12-3.0.5.jar, /home/mario/.ivy2/cache/org.scala-lang/scala-reflect/jars/scala-reflect-2.12.4.jar, /home/mario/.ivy2/cache/org.scala-lang.modules/scala-xml_2.12/bundles/scala-xml_2.12-1.0.6.jar),
parent = DualLoader(a = java.net.URLClassLoader#6307eb76, b = java.net.URLClassLoader#271053e1),
resourceMap = Set(app.class.path, boot.class.path),
nativeTemp = /tmp/sbt_fa64d1a1/sbt_66bd50e2
)
where again we can see
.../target/scala-2.12/test-classes
.../target/scala-2.12/classes
are present.
To actually pass the classpath to ProcessBuilder within a test:
import java.net.URLClassLoader
import sys.process._
class CubeCalculatorTest extends FunSuite {
test("CubeCalculator.cube") {
val classLoader = this.getClass.getClassLoader
val classpath = classLoader.asInstanceOf[URLClassLoader].getURLs.map(_.getFile).mkString(":")
s"java -classpath $classpath MyExternalApp".!
...
}
}
You can get the full classpath, to be used in the ProcessBuilder's JVM, from your sbt tests, with:
Thread
.currentThread
.getContextClassLoader
.getParent
.asInstanceOf[java.net.URLClassLoader]
.getURLs
.map(_.getFile)
.mkString(System.getProperty("path.separator"))

Functional Tests in Play 2.4.6 when using compile time DI

I'm using Play 2.4.6 with compile time dependency injection and ScalaTest. The controller's constructor has few parameters, and in an ApplicationLoader I create it.
Here is the code:
class BootstrapLoader extends ApplicationLoader {
def load(context: Context) = {
new AppComponents(context).application
}
}
class AppComponents(context: Context) extends BuiltInComponentsFromContext(context) with NingWSComponents {
lazy val router = new Routes(httpErrorHandler, authenticationController, applicationController, assets)
lazy val applicationController = new controllers.Application()
lazy val authenticationController = new controllers.Authentication()(configuration, wsApi.client)
lazy val assets = new controllers.Assets(httpErrorHandler)
}
class Authentication(implicit configuration: Configuration, val ws: WSClient) extends Controller {
def login = Action { implicit request =>
Unauthorized(s"${redirectUrl}")
}
}
class AuthenticationSpec extends PlaySpec with OneAppPerSuite {
implicit val configuration: Configuration = app.configuration
implicit val wsClient: WSClient = WS.client(app)
"when user not logged-in" should {
"return Status code Unauthorized(401) with redirect url" in {
1 mustEqual 2
}
}
}
When I'm running the test I'm getting the following error:
[info] Exception encountered when attempting to run a suite with class name: controllers.AuthenticationSpec *** ABORTED ***
[info] com.google.inject.ProvisionException: Unable to provision, see the following errors:
[info]
[info] 1) Could not find a suitable constructor in controllers.Authentication. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
[info] at controllers.Authentication.class(Authentication.scala:19)
[info] while locating controllers.Authentication
[info] for parameter 1 at router.Routes.<init>(Routes.scala:35)
[info] while locating router.Routes
[info] while locating play.api.test.FakeRouterProvider
[info] while locating play.api.routing.Router
[info]
[info] 1 error
[info] at com.google.inject.internal.InjectorImpl$2.get(InjectorImpl.java:1025)
[info] at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
[info] at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:321)
[info] at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:316)
[info] at play.api.Application$class.routes(Application.scala:112)
[info] at play.api.test.FakeApplication.routes(Fakes.scala:197)
[info] at play.api.Play$$anonfun$start$1.apply$mcV$sp(Play.scala:90)
[info] at play.api.Play$$anonfun$start$1.apply(Play.scala:87)
[info] at play.api.Play$$anonfun$start$1.apply(Play.scala:87)
[info] at play.utils.Threads$.withContextClassLoader(Threads.scala:21)
FakeApplication use GuiceApplicationBuilder, which of course does not work.
What should I do to run such tests?
Thanks
override implicit lazy val app = new BootstrapLoader().load(
ApplicationLoader.createContext(
new Environment(
new File("."), ApplicationLoader.getClass.getClassLoader, Mode.Test)))
It works in Play 2.5.1
You are getting an error because the tests are not even able to start a application. That is happening because you are using Dependency Injection in your controllers (as the error message suggests) and you need to declare them as classes, instead of as objects. As you can see at the docs:
package controllers
import play.api.mvc._
class Application extends Controller {
def index = Action {
Ok("It works!")
}
}
If your controller has some dependency to be injected, you should use the #Inject annotation in your controller constructor (again, please see the docs). Per instance:
package controllers
import play.api.mvc._
import play.api.libs.ws._
import javax.inject._
class Application #Inject() (ws: WSClient) extends Controller {
// ...
}
You can also read the Compile Time Dependency Injection docs if you are using it instead of runtime DI.
If you use specs2 you can do it. see http://loicdescotte.github.io/posts/play24-compile-time-di/
But you loose the nice api.
Scalatest / scalatest-plus has done something funky with the DI (guice) :(
I'm facing the same problem as you. I don't have a satisfying solution, the following is a mere workaround:
I ended up putting
implicit def client:WSClient = NingWSClient()
in my WithApplicationLoader class
I also found https://github.com/leanovate/play-mockws which allows you to mock ws calls. But that's not what we want here.
my guess would be that the OneAppPerSuite trait isn't using your custom application loader. you may need to override the application construction that comes from that trait and make it use your custom loader.
looks like there is an example using scalatest here: http://mariussoutier.com/blog/2015/12/06/playframework-2-4-dependency-injection-di/

Could not instantiate controllers.WebJarAssets during testing

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.