(Play 2.4) Dependency injection in a trait? - scala

In play 2.4, is it possible to use dependency injection in a trait ?
Is there any example ?
Thanks.

I talk about runtime DI with Guice here because it's the default method used by Play. Other DI methods or frameworks may differ here.
It isn't possible to inject a dependency into a trait because a trait isn't instantiable. A trait doesn't have a constructor to define the dependencies.
In Play you could use the injector directly as long as the Application trait is in scope. But this isn't considered good practice in production code. In test code this would be an option.
class MySpec extends PlaySpecification {
"My test" should {
"Use the injector" in new WithApplication extends Context {
val messages = Messages(Lang("en-US"), messagesApi)
}
}
trait Context extends Scope {
self: WithApplication =>
val messagesApi = app.injector.instanceOf[MessagesApi]
}
}

Related

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]

Manual Dependancy Injection App testing Play 2.5.x

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.

Mockito ignores my Specs2 sugared verify steps when traits are involved

Normally Specs2 sugared Mockito verifications are checked and fails the test when appropriate. However in some instances they are ignored.
Normally this test verification fails as expected as myApp called myService at least once.
import org.specs2.mock.Mockito._
class MySpec extends Specification with Mockito {
"MyApp" should {
"blow up" in WithApplication {
val myService = mock[MyService]
val myApp = new MyApp(myService)
myApp.doSomething
there was no(myService).doSomethingElse
}
}
}
(Note WithApplication is a Play! Framework thing)
However as I have hamfisted Cake Pattern traits into my components my tests look like this.
class MySpec extends Specification with Mockito {
"MyApp" should {
"blow up" in WithApplication with MockRegistry {
val myApp = new MyApp(myService)
myApp.doSomething
there was no(myService).doSomethingElse
}
}
}
where MockRegistry looks something like this
trait MockRegistry extends Mockito with MyServiceComponent {
val myService = mock[MyService]
}
My Cake patterned test does not fail verification, ever. I can change this to anything and they all get ignored.
there was no(myService).doSomethingElse
there was one(myService).doSomethingElse
there was two(myService).doSomethingElse
However by replacing the sugared mockito step with a direct call to the java methods it does fail when appropriate.
import org.mockito.Mockito._
verify(myService, times(1)).doSomethingElse
So it seems involving traits on the test method seems to have properly confused Mockito.
That's because the Mockito extension of MockRegistry doesn't know that exceptions needs to be thrown in case of failure. But the Mockito on Specification does because Specification has the ThrownExpectations trait mixed-in.
So you can either removed the Mockito extension from MockRegistry or add ThrownExpectations to it:
trait MockRegistry extends MyServiceComponent {
val myService = mock(classOf[MyService])
}
// or
import org.specs2.matcher.ThrownExpectations
trait MockRegistry extends MyServiceComponent with ThrownExpectations {
val myService = mock(classOf[MyService])
}
And as expected it was due to the traits. It was in face due too many Mockito trait-ing...
My spec had a mockito trait. My cake pattern mocked component registry also had a mockito trait. On the test method they were both part of the object, which seems to confuse Specs2/Mockito.
So by removing Mockito sugar from my mock component registry:
//import org.specs2.mock.Mockito._
import org.mockito.Mockito._
trait MockRegistry extends MyServiceComponent {
val myService = mock(classOf[MyService])
}
And only have the Mockito sugar in the Spec then my sugared verifications started to work as expected again.

Using Akka TestKit with Specs2

I'm trying to craft a specs2 test using Akka's TestKit. I'm stuck on a persistent compile error I can't figure out how to resolve, and I'd appreciate suggestions.
The compile error is:
TaskSpec.scala:40: parents of traits may not have parameters
[error] with akka.testkit.TestKit( ActorSystem( "testsystem", ConfigFactory.parseString( TaskSpec.config ) ) )
Following suggestions from Akka docs and internet xebia and Akka in Action, I'm trying to incorporate the TestKit into a specs2 Scope. Here's a snippet of the code where I'm getting the error:
class TaskSpec
extends Specification
with AsyncTest
with NoTimeConversions {
sequential
trait scope
extends Scope
with TestKit( ActorSystem( "testsystem", ConfigFactory.parseString( TaskSpec.config ) ) )
with AkkaTestSupport {
...
I have the following helper:
trait AkkaTestSupport extends After { outer: TestKit =>
override protected def after: Unit = {
system.shutdown()
super.after
}
}
Here is one thing you can do:
import org.specs2.mutable.SpecificationLike
import org.specs2.specification._
class TestSpec extends Actors { isolated
"test1" >> ok
"test2" >> ok
}
abstract class Actors extends
TestKit(ActorSystem("testsystem", ConfigFactory.parseString(TaskSpec.config)))
with SpecificationLike with AfterExample {
override def map(fs: =>Fragments) = super.map(fs) ^ step(system.shutdown, global = true)
def after = system.shutdown
}
This should avoid the compilation error you had because TestKit is an abstract class and it is only mixing-in traits: SpecificationLike is a trait (Specification isn't) and AfterExample is a trait.
Also the specification above runs in the isolated mode, meaning that there is a brand new TestSpec object instantiated for each example and the AfterExample trait makes sure that the system is shutdown after each example.
Finally the map method is overriden with a special step to make sure that the system created for the first TestSpec instance (the one declaring all the examples) will be cleanly disposed of.