Manual Dependancy Injection App testing Play 2.5.x - scala

I have project with manual dependency injection. Can I test my application with standard Play test suite?
play.application.loader="AppLoader"
class AppLoader extends ApplicationLoader {
override def load(context: Context): Application = {
LoggerConfigurator(context.environment.classLoader).foreach(_.configure(context.environment))
new AppComponents(context).application
}
}
}
class AppComponents(context: Context) extends BuiltInComponentsFromContext(context) with EhCacheComponents with EvolutionsComponents with DBComponents with HikariCPComponents{
lazy val applicationController = new controllers.Application(defaultCacheApi, dbApi.database("default"))
lazy val usersController = new controllers.Users(defaultCacheApi)
lazy val assets = new controllers.Assets(httpErrorHandler)
//applicationEvolutions
// Routes is a generated class
override def router: Router = new Routes(httpErrorHandler, applicationController, usersController, assets)
For now test is very simple
class ApplicationTest extends PlaySpec with OneAppPerTest {
"Application" must {
"send 404 on a bad request" in {
route(FakeRequest(GET, "/boum")) mustBe None
}
}
}
Test ends up with error:
Could not find a suitable constructor in controllers.Application. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument
I presume I need somehow use my AppLoader instead of defualt Guice mechanisam inside ApplicationTest class, because Application controller has dependacy ( cacheApi, dbApi ...)
route method can take application as argument but how can I obtain context to manually instantiate AppLoader class ? Being newbie in Scala recommendations are most welcomed.

This example answered all of my questions:
https://github.com/playframework/play-scala-compile-di-with-tests
Using term compile time dependency injection yield much more results then manual dependency injection.

Related

Scala, cake pattern and mixins. How to avoid many parameters in "injected" class

While building a new Playframework app I am trying to use the cake pattern.
My first understanding was to mix custom traits into the controllers and pass those provided by Play as parameter:
trait MyComponents {
def actorSystem: ActorSystemClassic
lazy val service = new MyService(actorSystem)
}
class MyController(
controllerComponents: ControllerComponents,
override val actorSystem: ActorSystemClassic) with MyComponents {
// ...
// Use `service` from MyComponents
}
class MyApp extends BuiltInComponentsFromContext(context) {
val controller = new MyController(
controllerComponents, // <- Provided by BuiltInComponentsFromContext
)
}
That worked fine until I had to test MyController and tried to mock the service.
To mock the service I should be able to work with a stub of MyComponents that will provide the mock. To provide that stub I have to pass it as constructor parameter.
class MyController(
controllerComponents: ControllerComponents,
myComponents: MyComponents,
override val actorSystem: ActorSystemClassic) {
// ...
// Use `myComponents.service`
}
Of course my controller is more complex than that and he need more than one component to work. My fear is to end with a constructor that will become hardly manageable, with a lot of parameters.
To limit the number of parameters, one idea would be to mix all of the components in one. However I am not able to mix the instance on ControllerComponents provided by the super class BuiltInComponentsFromContext with MyComponents:
class MyController(components: MyComponents with ControllerComponents)
class MyApp extends BuiltInComponentsFromContext(context) {
val controller = new MyController(
new MyComponents with /*instance of controllerComponents provided by BuiltInComponentsFromContext*/
)
}
I do not want to pass the controllerComponents to MyComponents because that class provide business services, she don not care about controller components.
Can you help me to implement a real life application with the cake pattern ?

Play Framework without dependency injection?

Without going into why, say someone wanted an old-fashioned Play Framework web service and did not want to use dependency injection nor rely on Google's Guice. Is it still possible in Play 2.8.x?
The api documentation along with the current Play examples recommend this as a "typical" HomeController.scala:
package controllers
import javax.inject._
import play.api.mvc._
class HomeController #Inject() (val controllerComponents: ControllerComponents) extends BaseController {
def index = Action {
Ok("It works!")
}
}
My desired code is the same, but without #Inject() (similar to when I last used Play 2.4.0 in 2016)? Back in the day my code looked like this:
package controllers
import play.api.mvc.{Action, AnyContent, Controller}
object TestController {
def index:Action[AnyContent] = Action {
Ok("It used to work.")
}
}
Console:
[info] Compiling 1 Scala source to /Volumes/.../play-scala-seed/target/scala-2.13/classes ...
[error] p.a.h.DefaultHttpErrorHandler -
! #7ef69nl6l - Internal server error, for (GET) [/test/] ->
play.api.UnexpectedException: Unexpected exception[CreationException: Unable to create injector, see the following errors:
1) Could not find a suitable constructor in controllers.TestController. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
at controllers.TestController.class(TestController.scala:3)
while locating controllers.TestController
for the 4th parameter of router.Routes.<init>(Routes.scala:33)
at play.api.inject.RoutesProvider$.bindingsFromConfiguration(BuiltinModule.scala:137):
Binding(class router.Routes to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)
1 error]
at play.core.server.DevServerStart$$anon$1.reload(DevServerStart.scala:210)
at play.core.server.DevServerStart$$anon$1.get(DevServerStart.scala:141)
at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:296)
at play.core.server.AkkaHttpServer.$anonfun$createServerBinding$1(AkkaHttpServer.scala:186)
at akka.stream.impl.fusing.MapAsync$$anon$30.onPush(Ops.scala:1261)
at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:541)
at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:423)
at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:624)
at akka.stream.impl.fusing.GraphInterpreterShell$AsyncInput.execute(ActorGraphInterpreter.scala:501)
at akka.stream.impl.fusing.GraphInterpreterShell.processEvent(ActorGraphInterpreter.scala:599)
Caused by: com.google.inject.CreationException: Unable to create injector, see the following errors:
1) Could not find a suitable constructor in controllers.TestController. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
at controllers.TestController.class(TestController.scala:3)
while locating controllers.TestController
for the 4th parameter of router.Routes.<init>(Routes.scala:33)
at play.api.inject.RoutesProvider$.bindingsFromConfiguration(BuiltinModule.scala:137):
Binding(class router.Routes to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)
1 error
at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:543)
at com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:159)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:106)
at com.google.inject.Guice.createInjector(Guice.java:87)
at com.google.inject.Guice.createInjector(Guice.java:78)
at play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:200)
at play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:155)
at play.api.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.scala:21)
at play.core.server.DevServerStart$$anon$1.$anonfun$reload$3(DevServerStart.scala:189)
at play.utils.Threads$.withContextClassLoader(Threads.scala:21)
Is there a simple fix to stay old school--without going here?
I acknowledge but don't totally understand https://www.playframework.com/documentation/2.4.x/Migration24. I assume my problem has to do with static routing having been removed in 2.7.
My reputation doesn't allow me to comment to the answer by Mario Galic, but you can easily modify his example by using the "right" (non-test) controllerComponents that are provided by BuiltInComponentsFromContext.
The whole example would look like
class HomeController(override protected val controllerComponents: ControllerComponents)
extends BaseController {
def index = Action { Ok("It works!") }
}
class MyApplicationLoader extends ApplicationLoader {
def load(context: ApplicationLoader.Context): Application = {
new BuiltInComponentsFromContext(context) {
lazy val homeController = HomeController(controllerComponents)
override lazy val router: Router = Routes(httpErrorHandler, homeController)
}.application
}
}
To answer comment by #kujosHeist about an equivalent example in Java. This seems to work for me (Following the Play docs):
package controllers;
import play.mvc.Controller;
import play.mvc.Result;
public class HomeController extends Controller {
public Result index() {
return ok("It works!");
}
}
public class MyApplicationLoader implements ApplicationLoader {
#Override
public Application load(Context context) {
return new MyComponents(context).application();
}
}
class MyComponents extends BuiltInComponentsFromContext implements HttpFiltersComponents, AssetsComponents {
public MyComponents(ApplicationLoader.Context context) {
super(context);
}
#Override
public Router router() {
HomeController homeController = new HomeController();
Assets assets = new Assets(scalaHttpErrorHandler(), assetsMetadata());
return new router.Routes(scalaHttpErrorHandler(), homeController, assets).asJava();
}
}
You might want to define things like e.g. error handlers differently, but this could be roughly the structure.
Indeed StaticRoutesGenerator has been removed which is need to have controllers as singleton objects. Perhaps using compile time dependency injection, with an example here, might bring you closer to what you were used to, however ControllerComponents will still need to be injected. Technically, it might be possible to do something ill-advised by putting play-test on the Compile classpath and make use stubControllerComponents like so
class HomeController extends BaseController {
def index = Action { Ok("It works!") }
override protected def controllerComponents: ControllerComponents =
play.api.test.Helpers.stubControllerComponents()
}
and corresponding minimal ApplicationLoader
class MyApplicationLoader extends ApplicationLoader {
def load(context: ApplicationLoader.Context): Application = {
new BuiltInComponentsFromContext(context) {
override def httpFilters: Seq[EssentialFilter] = Nil
lazy val homeController = new _root_.controllers.HomeController
lazy val router: Router = new _root_.router.Routes(httpErrorHandler, homeController)
}.application
}
}
This way HomeController, although still a class, is now completely hardwired, and there is only a single instance of it created in ApplicationLoader.
Personally, I would advise against such shenanigans, and believe there are good arguments why Play moved away from singletons, for example, testability, thread-safety, etc.

Error trying to inject a dependency in Lagom

I'm trying to create a simple service to send emails using Lagom framework and the Scaladsl. I'm trying to use the Play Mailer Plugin to handle emails but I'm struggling trying to inject it into the service implementation.
I created the service trait and implementation passing the mailerClient as a dependency in the constructor.
trait MailerService extends Service { ... }
class MailerServiceImpl(mailerClient: MailerClient, persistentEntityRegistry: PersistentEntityRegistry) extends MailerService {
...
}
I'm wiring the service in the ApplicationLoader, following the pattern explained in the Lagom documentation and in the hello world application using macwire.
abstract class MailerApplication(context: LagomApplicationContext)
extends LagomApplication(context)
with CassandraPersistenceComponents
with AhcWSComponents {
override lazy val lagomServer: LagomServer = serverFor[MailerService](wire[MailerServiceImpl])
override lazy val jsonSerializerRegistry = MailerSerializerRegistry
persistentEntityRegistry.register(wire[MailEntity])
}
When I try to compile, I get the following error.
[error]
/.../workspace/mailer/mailer-impl/src/main/scala/com/example/mailer/impl/MailerApplicationLoader.scala:92:
Cannot find a value of type: [play.api.libs.mailer.MailerClient]
I thought macwire would be able to sort out the dependencies from the constructor but it looks like it's not really. I've tried different options, like trying to wire it explicitly in the application loader without any success so far.
I'm pretty sure there's something I'm getting wrong about how DI works in Lagom but I cannot figure it out.
Any ideas?
For the MailerClient to be injectable, you need to mix in the MailerComponents trait along with the other traits you use in your service.
For example:
// ...
import play.api.libs.mailer._
abstract class MailerApplication(context: LagomApplicationContext)
extends LagomApplication(context)
with MailerComponents // add this here
with CassandraPersistenceComponents
with AhcWSComponents {
override lazy val lagomServer: LagomServer = serverFor[MailerService](wire[MailerServiceImpl])
override lazy val jsonSerializerRegistry = MailerSerializerRegistry
persistentEntityRegistry.register(wire[MailEntity])
}
This is described in the Play Mailer documentation on compile-time injection

Play Slick: How to inject DbConfigProvider in tests

I am using Play 2.5.10, Play-slick 2.0.2, and my activator-generated project comes with scalatest and code like this:
class TestSpec extends PlaySpec with OneAppPerSuite {...}
I managed to test routes/Actions; now I would test DAO methods on a lower level. I searched the web and SO for a solution, and could not find any that is still up-to-date. A DAO signature is like this:
class TestDAO #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile]
so I need to pass it the dbConfigProvider thing.
For some reason I can't inject the provider into the tests like we do in controllers (no error, tests just won't run):
class TestSpec #Inject()(dbConfigProvider: DatabaseConfigProvider) extends PlaySpec with OneAppPerSuite {...}
The Play-Slick docs say we can alternatively use a global lookup
val dbConfig = DatabaseConfigProvider.get[JdbcProfile](Play.current)
but it won't work directly because
There is no started application
and link to an example project doing that:
class TestDAOSpec extends Specification {
"TestDAO" should {
"work as expected" in new WithApplicationLoader { // implicit 'app'
val app2dao = Application.instanceCache[TestDAO].apply(app)
but I could never find the WithApplicationLoader. Instead, there seems to be a WithApplication:
class TestDAOSpec extends Specification {
"TestDAO" should {
"work as expected" in new WithApplication() { // implicit 'app'
val app2dao = Application.instanceCache[TestDAO].apply(app)
but then I get
Type mismatch: expected a play.api.Application, got: play.Application.
At this point I lost hope.
How can I test a DAO?
N.B. I don't need to switch databases for testing (I handle this via config), I just want to access the default database in tests.
You can use:
lazy val appBuilder: GuiceApplicationBuilder = new GuiceApplicationBuilder().in(Mode.Test)
lazy val injector: Injector = appBuilder.injector()
lazy val dbConfProvider: DatabaseConfigProvider = injector.instanceOf[DatabaseConfigProvider]

How to Test a Play Application that extends a custom trait

I'm having trouble writing tests for a mixin to my Play application that runs in it's own thread separate from play. I've tried over-writing WithApplication.provideApplication method with no luck. I get an inheriting conflicting methods error. (one from the real app "MyRunnableSystemWrapper", one from my mocked fake mixin called "MyMockedSystemWrapper").
execute(system) runs my system that is tested elsewhere and has sideaffects (connects to networked services, thus failing this test when such things are not available. Good news is I have a mocked service of my system wrapper that uses a system which does NOT have side affects and DB/Network calls are mocked out. However I do not know how to give THIS MOCKED version of my app to "WithApplication" test.
Reduced Code for clarity:
class Application extends Controller with MyRunnableSystemWrapper {
val pool: ExecutorService = Executors.newFixedThreadPool(1)
val system = new MyRunnableSystem() //system is abstract in MRSW ^^^ above
pool.execute(system)
def index = Action {
OK("HI")
}
}
My Test:
class MyAppTest(implicit ee: ExecutionEnv) extends Specification {
abstract class WithMyMockApp extends WithApplication {
def provideApplication = new controllers.Application with MyMockedSystemWrapper // This imports MyRunnableSystemWrapper
}
"Sending a GET request" should {
"Respond with OK" in new WithMyMockApp {
val response = route(app, FakeRequest(GET, "/")).get
status(response) mustEqual OK
}
}
}
If I'm not running my Runnable in the correct place and should be calling it somewhere else to make this testing easier, let me know!
You could inject your system wrapper instead of extending it
trait SystemWrapper {
def execute(system: RunnableSystem)
}
class MyRunnableSystemWrapper extends SystemWrapper {...}
class MyMockedSystemWrapper extends SystemWrapper {...}
class Application #Inject() (systemWrapper SystemWrapper) extends Controller {
Then you need to tell Guice which implementation of SystemWrapper you want for runtime and which one for test. One way of doing this is by using different Guice modules for runtime/test which you set in your .conf files.