I'm new to play framework and have limited experience with scala. Using play framework 2.4 I'm trying to write full integrations tests where I want to call controller, and using in-memory db retrieve data from it. I'm using scalatest-plus together with scalatest. I have read about play unit tests in play documentation and now trying to use play.api.test.FakeApplication but i cannot find a way to inject my guice module to this fake application. I'm ussing OneAppPerSuite trait I can override FakeApplication but cannot pass any guice module for injection. Here is FakeApplication source from:
case class FakeApplication(
override val path: java.io.File = new java.io.File("."),
override val classloader: ClassLoader = classOf[FakeApplication].getClassLoader,
additionalPlugins: Seq[String] = Nil,
withoutPlugins: Seq[String] = Nil,
additionalConfiguration: Map[String, _ <: Any] = Map.empty,
withGlobal: Option[play.api.GlobalSettings] = None,
withRoutes: PartialFunction[(String, String), Handler] = PartialFunction.empty) extends Application {
private val app: Application = new GuiceApplicationBuilder()
.in(Environment(path, classloader, Mode.Test))
.global(withGlobal.orNull)
.configure(additionalConfiguration)
.bindings(
bind[FakePluginsConfig] to FakePluginsConfig(additionalPlugins, withoutPlugins),
bind[FakeRouterConfig] to FakeRouterConfig(withRoutes))
.overrides(
bind[Plugins].toProvider[FakePluginsProvider],
bind[Router].toProvider[FakeRouterProvider])
.build
....
So there is no way for me to pass custom module here.
Here is how my test looks like:
class UsersControllerTest extends PlaySpec with OneAppPerSuite {
override implicit lazy val app = FakeApplication(additionalConfiguration = inMemoryDatabase())
"Application " should {
"work" in {
val resp = route(FakeRequest(GET, "/api/users")).get
}
}
}
And here is my controller I want to test:
class UserController #Inject()
(userService: UserService)
(implicit ec: ExecutionContext) extends Controller {
def get = Action.async { implicit request => {
// implementation
}
}
}
Obviously my test now fail since it cannot find UserService instance.
So my question is:
How to properly write end2end integration tests with play using guice context
Is it common to write such tests in play application, since I cannot find any decent examples or documentation of doing something like that. I used to write such tests in java web applications where integration tests load spring context, but can't find such examples in play.
Thanks in advance.
If you want to test your default configuration, starting FakeApplication, is enough. It loads modules specified in application.conf automatically. So, If it can't find UserService, that means you don't have proper module configurations in your application.conf and there for your server can't serve those requests.
https://www.playframework.com/documentation/2.4.x/ScalaDependencyInjection
If you want to use some Mockups, and override default configurations:
https://www.playframework.com/documentation/2.4.x/ScalaTestingWithGuice
Related
I have a service class, and the service have one method getSomethingFromApi , now , I want to have play Configuration instance so I can pull stuff from the application.conf, and a play WSClient so I can perform http calls.
this is how I want my service to look:
class MyApiService {
def getSomethingFromApi(whichApi: String): Future[ApiRes] = {
wsClient.url(configuration.getString(whichApi)).withHttpHeaders(("Content-Type", "application/json")).get.map { res =>
response.status match {
case Status.OK => // do something
case _ => throw new Exception
}
}
}
}
and this is the ServicesModule that is wiring my services:
import com.softwaremill.macwire._
trait ServicesModule {
lazy val myService: MyApiService = wire[MyApiService]
}
my question now is what is the right way of using wiring play Configuration and WSClient instances..? cause currently i need those instances in my service but i dont have them, how should i do this the right way?
thanks
With macwire it'll probably look like this
// MyApiService.scala
class MyApiService(wsClient: WSClient) { ... }
// ServicesModule.scala
trait ServicesModule with NingWSComponents {
lazy val wsClient = wire[WSClient]
lazy val apiService = wire[MyApiService]
}
I haven't tried using macwire with play myself, so I have relatively low confidence that it'll work on the first try, but macwire play example suggests mixing in certain Play modules to provide values needed for WSClient. Most likely not all of them are needed, but some might be - soo I'd suggest starting with just NingWSComponents and gradually adding more until it works.
For the configuration I suggest using something like PureConfig and load the configuration as follows
import pureconfig._
import pureconfig.error.ConfigReaderFailures
case class YourConfClass(name: String, quantity: Int)
val config: Either[pureconfig.error.ConfigReaderFailures,YourConfClass] = loadConfig[YourConfClass]
This then can be passed on to any component of your app using macwire.
As of Play 2.6.X one should use AhcWSComponents that are provided by the ws dependency as follows:
In your build.sbt file add the ws dependency to your project
libraryDependencies += ws
In your module trait mix-in the AhcWSComponents trait and wire the WSClient
trait ServicesModule with AhcWSComponents {
lazy val wsClient = wire[WSClient]
lazy val apiService = wire[MyApiService]
}
In your MyApiService add the WSClient as a param. to the constructor
class MyApiService(wsClient: WSClient) { ... }
And now you're done. This general rule applies to all provided dependencies.
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 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]
I'm trying to test some actions. They require my security stuff which I've created using Silhouette.
I have a module to configure the DI for Silhouette in security.Module, and in my conf/application.conf file I have the line:
play.modules.enabled += "security.Module"
When I run my tests I get an error saying:
No implementation for com.mohiva.play.silhouette.api.Silhouette<security.JwtEnv> was bound.
If I set a breakpoint in my security.Module class, it's never triggered. However if I set a breakpoint in my main Module class it is. So I can see that my security.Module is never being loaded despite the docs for GuiceApplicationBuilder saying enabled modules are loaded automatically.
Here's how I'm configuring GuiceApplicationBuilder in my test class (based on scalatest):
class TestUserController extends PlaySpec with OneAppPerTest with Results {
val identity = UserDao(
id = Some(10),
email = Some("testuser#example.com"),
createdAt = DateTime.parse("2016-11-11T11:11:11")
)
val loginInfo = LoginInfo("credentials", identity.email.get)
implicit val env = FakeEnvironment[JwtEnv](Seq(loginInfo -> identity))
implicit override def newAppForTest(td: TestData) = new GuiceApplicationBuilder()
.overrides(bind[UsersBlockedRepo].to[MockUsersBlockedRepo])
.build
//...
}
What am I missing to get GuiceApplicationBuilder to load my security module?
I've fixed this. The problem was that my security.Module took constructor args:
class Module(environment: PlayEnvironment,
configuration: Configuration) extends AbstractModule with ScalaModule {...}
Removing those fixed it.
I'm working on Playframework2.5 with play-slick and programs related to it such as batch.
current project structure is like
/rootPlayProject
/app
/controllers
/filters
/services
...
Modules
/core (sub-project - DAOs,services are placed here)
/batch (sub-project depends on core)
I'm using Guice DI almost everywhere include Database Access Object(DAO).
And interfaces in core are bound in Module placed in core which end up getting inherited by Module in root project.
Core Module(/rootPlayProject/core/CoreModule.scala)
class CoreModule extends AbstractModule {
override def configure() = {
bind(classOf[FooDAO]).to(classOf[FooDAOImpl])
....
}
}
Root Module(/rootPlayProject/Modules.scala)
class Module extends CoreModule {
override def configure() = {
super.configure()
bind(classOf[FooService]).to(classOf[FooServiceImpl])
}
}
This works pretty well as Playframework application though, I would like to use core module for batch programs and I would like to run the batches without playframework.
so far I tried something like this
object BatchA {
def main(args: Array[String]) = {
val injector = Guice.createInjector(new CoreModule)
val foo = injector.getInstance(classOf[FooDAO])
//do something with foo.
}
}
but since my DAO's requiring things Playframework create such as ExecutionContext,play.api.db.slick.DatabaseConfigProvider and #play.db.NamedDatabase, above code does not run.
My question is, How can I let those things get bound without play application builder?
Thanks in advance.
The answer depends on whether or not you want to actually decouple Play Framework from your DAOs.
Option 1: Don't Decouple
Your main method could simply have the following lines preceeding your val injector line:
val application = new GuiceApplicationBuilder()
.in(Environment(new File("."), this.getClass.getClassLoader, Mode.Prod))
.build
Play.start(application)
Option 2: Decouple
Or, you can provide injectable classes that can provide the ExecutionContext specific to the environment. If you want to inject the DatabaseConfigProvider, you will have to perform further abstractions to remove the direct dependency on Play. The annotation will follow the Play-specific implementation of the abstraction.
In the case of my own project where I encountered this scenario, I opted for Option 1, as the dependency on Play wasn't severe enough of an impact for me.
GuiceInjectorBuilder do the trick.
trait PlayInjector {
lazy val injector = new GuiceInjectorBuilder().configure(Configuration.load(Environment.simple(mode = Mode.Dev)))
.bindings(new BuiltinModule, new CoreModule, new SlickModule).disable(classOf[Application]).injector
def closeInjector = injector.instanceOf[DefaultApplicationLifecycle].stop
}
BuiltinModule binds Play basic modules such as ExecutionContext, ExecutionContextExecutor or ActorSystem.
You can make your own Module to bind only things you need to though, using BuiltinModule and disable classes you don't need is simpler.
how to use it
object Foo extends PlayInjector {
def main(args: Array[String]) = {
val foo = injector.instanceOf[FooDAO]
val bar = injector.instanceOf[BarDAO]
//do something
//when you finish things you want to do
closeInjector
}
}
Since some modules like PlaySlick uses ApplicationLifecycle.addStopHook to handle closing operation. It's safer to execute them instead of just call sys.exit()