Play 2.4 discourages using GlobalSettings.onStart and whole Global object.
I'm using play-slick and it has great DI sample in GitHub, but it is missing a example how to do database initialization.
How does one implement the database initialization when using DI and when GlobalSettings.onStart is not available?
Common cases for database initialization is: If in DEV mode, add these rows, if PROD do these. Examples wanted.
The trick is to place the initialisation in the constructor of the injected class. Here's an example:
Add app/modules/Database.scala:
package modules
import com.google.inject.AbstractModule
import com.google.inject.name.Names
trait Database {
def create(): Unit
def drop(): Unit
}
class TestDatabase extends Database {
initialize() // running initialization in constructor
def initialize() = {
println("Setup database with test data here")
}
def create() = ()
def drop() = ()
}
class ProdDatabase extends Database {
// similar to above
}
class DatabaseModule extends AbstractModule {
def configure() = {
bind(classOf[Database])
.annotatedWith(Names.named("development"))
.to(classOf[TestDatabase]).asEagerSingleton
bind(classOf[Database])
.annotatedWith(Names.named("production"))
.to(classOf[TestDatabase])
}
}
Add in conf/application.conf:
play.modules.enabled += "DatabaseModule"
That's to start with. The .asEagerSingleton will run the constructor code without needing you to inject it. When you want to choose which one to inject, then you'd need to remove the .asEagerSingleton and load the appropriate database implementation either:
based on configuration in the bindings (see link for example); or
in the service/controller, for example:
#Inject #Named("development") Database database
Related
This is regarding a Play 2.3 application that I am trying to introduce Guice into.
I have the following legacy code to Guicify and I reached this point
class DatabaseService {
def db: Database = DB(play.api.Play.current)
//More code below.
}
This class is injected in the class under test, say com.TestA.
When I run the test cases using a modified OneAppPerTest setup, I see ProvisionExceptions of the following kind.
Error injecting constructor, java.lang.RuntimeException: There is no started application
Is it because of the use of play.api.Play.current?
How do I fix this issue?
Additional Info
The overridden OneAppPerTest variant, uses the following logic
override def newAppForTest(testData: TestData): FakeApplication = {
new FakeApplication(
additionalConfiguration = additionalConfiguration,
withGlobal = Some(globalSettings)
)
}
where additionalConfiguration can be overridden by actual tests and globalSettings is where Guice is introduced within the framework
protected def globalSettings = {
new TestGlobalSettings {
val injector = Guice.createInjector(overriddenModules: _*)
override def getControllerInstance[A](controllerClass: Class[A]): A = {
injector.getInstance(controllerClass)
}
override def getInjector = {
injector
}
}
}
There are other test cases with a similar setup that work perfectly fine. I am able to inject mocks, test the classes. However, every test with this DatabaseService class is failing, because I can't inject it cleanly.
The issue is related to how Guice creates the objects and when. DatabaseService here had access to the current application, the current configuration. However, in my Global.scala, I had it injected before 'onStart' was actually called.
This implies that we are trying to access an application before it was properly initialized.
Making DatabaseService injection in the test class lazy helped avoid the error.
I'm continuing my exploration of the Play framework and its related components. I used the template for CRUD application with a connection to a PostgreSQL database to begin with. It splits the application in models, repositories, controllers and views. This works well.
Now, I'm trying to create some tests for this application with Specs2. More precisely, I'm trying to test the repository. It is defined as follow:
package dal
import javax.inject.{ Inject, Singleton }
import play.api.db.slick.DatabaseConfigProvider
import slick.driver.JdbcProfile
import models.Cat
import scala.concurrent.{ Future, ExecutionContext }
#Singleton
class CatRepository #Inject() (dbConfigProvider: DatabaseConfigProvider)(implicit ec: ExecutionContext) {
...
}
I would like to set an in memory database which would be created (schema, evolutions) before all tests, destroyed after all tests, populated (data, probably with direct SQL) and flushed around each test. I would like to pass it on to my repository instance which I would then use to perform my test. Like:
val repo = new CatRepository(inMem_DB)
So how do I go about:
1) creating this db and applying the evolutions?
maybe:
trait TestDB extends BeforeAfterAll {
var database: Option[Database] = None
def before = {
database = Some(Databases.inMemory(name = "test_db"))
database match {
case Some(con) => Evolutions.applyEvolutions(con)
case _ => None
}
println("DB READY")
}
def after = {
database match {
case Some(con) => con.shutdown()
case _ => None
}
}
}
Using a var and always making a "match/case" when I need to use the db isn't convenient. I guess there is a much better to do this...
2) Populate and flush around each test?
Shall I create a trait extending Around, just as with BeforeAfterAll?
3) Create on of these play.api.db.slick.DatabaseConfigProvider from the database?
Any link showing how to do this?
I found few examples which where covering this with running a FakeApplication, but I assume there is a way to pass somehow the db to such repository object outside of a running application..?
Thank you for helping.
Cheers!
You can use a lazy val and AfterAll for the database setup/teardown, then BeforeAfterEach for each example:
trait TestDB extends AfterAll with BeforeAfterEach {
lazy val database: Database =
Databases.inMemory(name = "test_db")
def afterAll =
database.shutdown
def before =
database.populate
def after =
datbase.clean
}
I im trying to print "Hello" to console on application start. Can You explain how to do it?
What i tried myself:
app/modules/HelloModule.scala:
package modules
import com.google.inject.AbstractModule
trait Hello {}
class MyHelloClass extends Hello {
initialize() // running initialization in constructor
def initialize() = {
println("Hello")
}
}
class HelloModule extends AbstractModule {
def configure() = {
bind(classOf[Hello])
.to(classOf[MyHelloClass]).asEagerSingleton
}
}
in conf/application.conf i added:
play.modules.enabled += "modules.HelloModule"
and "Hello" is not printed when i run activator run
You need to use Global object, and override "onStart" method:
Defining a Global object in your project allows you to handle global
settings for your application. This object must be defined in the
default (empty) package and must extend GlobalSettings.
import play.api._
object Global extends GlobalSettings {
override def onStart(app: Application) {
Logger.info("Application has started")
}
override def onStop(app: Application) {
Logger.info("Application shutdown...")
}
}
You can also specify a custom GlobalSettings implementation class name
using the application.global configuration key.
Update:
The correct way would be to use Dependency Injection, exactly like it described in the question. GlobalSettings could be removed later
There is no problem with the code in the question. I verified it on my local setup. The code write "Hello" after first request in the development mode "activator run" and after application start in the production mode "activator start".
Btw, try to use some more easy to find string in the log, like
"--------APP DZIABLO HAS BEEN STARTED--------"
It could be so you just missed "Hello" in the log (I did not recognise it from the start)
A typical service looks like this:
trait BaseService extends LazyLogging {
def getDb() = {
DatabaseHelper.getDb // database for the scala slick library
}
}
abstract class UserService extends BaseService {
def getById(userId: Int): Option[User]
}
class UserServiceImpl #Inject(val userDao: UserDao) extends UserService = {
def getById(userId: Int): Option[User] = {
getDb().withSession { implicit session =>
return userDao.getById(userId)
}
}
}
Using Guice I wire up my objects like:
class ServiceModule extends ScalaModule {
def configure() {
bind[UserDao].to[UserDaoImpl]
bind[UserService].to[UserServiceImpl]
}
}
Now when I am unit testing using scalatest, I am a bit confused how I can de-couple the database access since I want to mock the database responses.
My spec looks like:
class UserServiceSpec extends UnitSpec with MockitoSugar {
val userService = injector.getInstance(classOf[UserService])
describe("UserServiceSpec") {
it("should do someting") {
val abc = userService.doSomething();
abc.name should be("abc")
}
}
}
My UnitSpec class wires up my Guice.
I am confused, where should I create the mock objects (using mockito) and how should I wire them using Guice? In the ServiceModule or?
My design seems wrong since my BaseService has a connection to the database, I need to refactor that out somehow.
Looking for a way to get out of this bad design I currently seem to have, ideas?
You can move the db connection to DAO layer. Your application should have three layers: controller -> service -> DAO. All the service layer needs to know is functionalities provided by DAO, i.e. CRUD operations; it should not know anything about db connection since that is DAO's responsibility.
I am not really sure about Slick framework, but for Play framework with Guice, it allows to disable "real" bindings (injected dependencies) that you expect when the application is running and enable bindings that are only for testing like this:
implicit override lazy val app = new GuiceApplicationBuilder()
.configure(appConfig)
.disable(classOf[ReactiveMongoModule], classOf[CommonModule])
.bindings(bind(classOf[ReactiveMongoApi]).toInstance(api))
.bindings(TestDaoModule())
.build()
This is a complete integration test (controller layer), hope that it helps: https://github.com/luongbalinh/play-mongo/blob/master/test/controllers/UserControllerTest.scala
I need to execute a code allowing the launch of scheduled jobs on start of the application, how can I do this? Thanks.
Use the Global object which - if used - must be defined in the default package:
object Global extends play.api.GlobalSettings {
override def onStart(app: play.api.Application) {
...
}
}
Remember that in development mode, the app only loads on the first request, so you must trigger a request to start the process.
Since Play Framework 2.6x
The correct way to do this is to use a custom module with eager binding:
import scala.concurrent.Future
import javax.inject._
import play.api.inject.ApplicationLifecycle
// This creates an `ApplicationStart` object once at start-up and registers hook for shut-down.
#Singleton
class ApplicationStart #Inject() (lifecycle: ApplicationLifecycle) {
// Start up code here
// Shut-down hook
lifecycle.addStopHook { () =>
Future.successful(())
}
//...
}
import com.google.inject.AbstractModule
class StartModule extends AbstractModule {
override def configure() = {
bind(classOf[ApplicationStart]).asEagerSingleton()
}
}
See https://www.playframework.com/documentation/2.6.x/ScalaDependencyInjection#Eager-bindings
I was getting a similar error.
Like #Leo said, create Global object in app/ directory.
Only thing I had to make sure was to change "app: Application" to "app: play.api.Application".
app: Application referred to class Application in controllers package.