I am new to scala and I am using it with Playframework to build an API.
I have multiple docker containers (API, DB). Also, there is a module where DB configuration are specified. In this configuration, I use docker container name db in setJdbcUrl.
Everything works perfectly when running things within docker containers.
However, I have a healthcheck unit test that tests a route.
This test fails because it cant recognize the URL (as expected), but I am not sure if there is a way to update the fake application in the test to use the correct URL.
The Module
class ReadWriteDB extends ScalaModule {
#Singleton
#Provides
#Named("db.sql.readwrite.quill.context")
def quillContext(): PostgresJdbcContext[SnakeCase.type] = {
val ds = new HikariDataSource(DBConnectionConfig.hikariConfig())
QuillFactory.create(ds, Duration("2m"))
}
object DBConnectionConfig {
def hikariConfig(): HikariConfig = {
val config = new HikariConfig
config.setDriverClassName("org.postgresql.Driver")
config.setJdbcUrl("jdbc:postgresql://db:5432/postgres")
// config.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres")
// with the commented config, unit test runs correctly
config.setUsername(...)
config.setPassword(...)
config
}
}
The unit test
class HealthCheckSpec extends PlaySpec with GuiceOneAppPerTest with Injecting {
"HealthCheck status" should {
"reply from the router" in {
// can I edit app to use a different URL here?
val request = FakeRequest(GET, "/status")
val home = route(app, request).get
status(home) mustBe OK
contentAsString(home) must include("API is running!")
}
}
}
Controller
#Singleton
class HealthCheck #Inject()(val controllerComponents: ControllerComponents) extends BaseController {
def check(): Action[AnyContent] = Action {
Ok("API is running!\n")
}
}
Any guidelines would be appreciated.
You have all the tools you need. I would turn the object into a class and use #provides and #singleton like you have in your quillContext, and I'd also pull out the behaviour into a trait and have a method for just is the healthcheck okay:
trait DBObjectConfig {
def hikariConfig(): HikariConfig
def isOK() :Boolean
}
#provides
class RealDBConnection extends DBObjectConfig {
def hikariConfig(): HikariConfig = {
val config = new HikariConfig
config.setDriverClassName("org.postgresql.Driver")
config.setJdbcUrl("jdbc:postgresql://db:5432/postgres")
config.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres")
config.setUsername(...)
config.setPassword(...)
config
}
// todo: make something here that's a reliable test.
def isOK() :Boolean = {
Option(hakariConfig.getConnection()).isDefined
}
}
Your healthcheck controller can use isOK() to see if the db is up or not.
Then in your test, using Guice (your test extends an injector?) you should be able to bind a fake implementation of DBObjectConfig to your app, which will cause your controller to not instantiate a real db connection in the testing environment:
// inside your tests.
class MockDB extends DBObjectConfig {
def hikariConfig(): HikariConfig = ??? // implementation not required
def isOK() :Boolean = true // fake it being okay.
}
val application = new GuiceApplicationBuilder()
.overrides(bind[DBObjectConfig].to[MockDB])
.build()
Some technique using this general approach should work.
To implement Health Check I would recommend Play-Actuator dependency.
Related
I have a controller which looks like
class MyController #Inject()(service: MyService,
cc: MessagesControllerComponents
)(implicit ec: ExecutionContext)
extends MessagesAbstractController(cc) {
def getAll ....// all methods of controller
Now, I am trying to unit test the controller using Mockito and Scalatest where I am trying to inject the mocked object of MyService in unit test. My unit test is as follows
class MyControllerTest extends PlaySpec with GuiceOneAppPerSuite {
"MyController" should {
def fakeApplication(): Application = new GuiceApplicationBuilder().build()
"not return 404" when {
"we try to hit the route /ads" in {
val fakeRequest = FakeRequest(GET, "/ads")
val futureResult: Future[Result] = route(fakeApplication, fakeRequest).get
val resultJson: JsValue = contentAsJson(futureResult)(Timeout(2, TimeUnit.SECONDS))
resultJson.toString mustBe """{"status":"success"}"""
}
}
}
}
Now to unit test the controller, I need to pass in the mock of the service in the controller while building it via guice. I tried the following method to inject the mocked dependency in controller,
val application = new GuiceApplicationBuilder()
.overrides(bind[MyService])
.build
However, it fails to inject the mocked service object. Any pointers on where I am going wrong will be highly appreciated. Thanks in advance.
You have to do something like
val application = new GuiceApplicationBuilder()
.overrides(bind[MyService].toInstance(yourMock))
.build
I have an external library (scala-redis) that requires an implicit ActorSystem when initializing the client. I would like to have my RedisClient as a Singleton inside my Play (2.6) Application because it would make sense having it as a Singleton.
class CustomAppModule(environment: Environment,
configuration: Configuration) extends AbstractModule {
def configure() = {
//val system = getProvider(classOf[ActorSystem]).get()
//val system = ActorSystem()
//bind(classOf[ActorSystem]).toInstance(system)
val redis = RedisClient(configuration.get[String]("redis.host"))(system)
bind(classOf[RedisClient]).toInstance(redis)
}
}
First system fails because of "Provider cannot be used until the Injector has been created", and second system fails because Play Framework initializes the ActorSystem itself when the application starts, and second system fails because of "binding to akka.actor.ActorSystem was already configured at play.api.inject.BuiltinModule".
So what would be the idiomatic way with Guice/DI to proceed with this kind of situation? Do I need a wrapper Singleton that has the RedisClient as a value, and where the ActorSystem can be injected?
I think the provides method will solve your problem. Write your module as
class MyModule extends AbstractModule {
def configure() = {
}
#Provides
#Singleton
def givePrecious() : MyClass = {
new MyClass()
}
}
Here my Class looks like
#Singleton
class MyClass(a: String) {
def this() = {
this("a")
println("constructor called")
}
}
Now I try to create 3 instances of this class
val injector = Guice.createInjector(new MyModule())
val precious1 = injector.getInstance(classOf[MyClass])
val precious2 = injector.getInstance(classOf[MyClass])
val precious3 = injector.getInstance(classOf[MyClass])
You will see that the string "constructor called" is printed only once.
For sake of simplicity I have make a as a string. you can try to make it an instance of ActorSystem.
I'm trying to make a functional test for a controller with the play framework version 2.5.9.
I have a controller called HomeController
#Singleton
class HomeController #Inject()(implicit exec: ExecutionContext) extends Controller {
def index = Action.async {
Future{
Ok(views.html.index("Home"))
}
}
}
my view looks like this
#(message: String)
#main("Home") {
<h2>Welcome</h2>
}
the arguments in #main are what goes in the title of the page
and my test looks like this
class ApplicationTwoSpec extends PlaySpec with OneServerPerSuite with OneBrowserPerSuite with HtmlUnitFactory{
implicit val ec = Implicits.global
val homeController = new HomeController()
implicit override lazy val app =
new GuiceApplicationBuilder()
.router(Router.from{
case GET(p"/") => homeController.index
})
.build()
"The sample Controler with a server" must {
"have home in the title" in {
go to s"http://localhost:9000/"
pageTitle mustBe "Home"
}
}
}
when I run the test I get back
must return and ok response back *** FAILED ***
"[]" was not equal to "[Home]" (ApplicationTwoSpec.scala:37)
What do I need to do to have it yeild a proper response?
I found the problem after going to the source code of both the scala test plus and playframework.
go to s"http://localhost:9000/"
should be
go to s"http://localhost:$port/"
An implicit lazy val port is specified in OneServerPerSuite trait and gets its default value from the play.api.test.Helpers package which defaults to port 19001. If I really needed the port to be 9000 I could also override the port by using
implicit override lazy val port = 9000
using play 2.5 and guice i have managed to successfully inject applicationConfig into a singleton class and reference a config variable inside it,
trait TMongoFactory{
val SERVER: String
val PORT: Int
val DATABASE: String
val connection: MongoClient
val collection: MongoDB
}
#Singleton
class MongoFactory #Inject()(val configuration: Configuration) extends TMongoFactory{
val SERVER = "localhost"
val PORT = 27017
val DATABASE = configuration.underlying.getString("connectionString")
val connection = MongoClient(SERVER, PORT)
val collection = connection(DATABASE)
}
class MongoModule extends AbstractModule {
def configure() = {
bind(classOf[TMongoFactory]).to(classOf[MongoFactory])
}
}
I can then pass this singleton to a repository class like so
#Singleton
class MongoRemainingAllowanceRepository #Inject()(MongoFactory: TMongoFactory) extends RemainingAllowanceRepository{
val context = MongoFactory.collection("remainingAllowance")
def save(remainingAllowance: RemainingAllowance): Unit ={
context.save(RemainingAllowance.convertToMongoObject(remainingAllowance))
}
This all works fine and as expected, but the problem is i need to call this repository in the test suite so i dont want it to have to take any arguments (specifically injected ones).
So i tried to change it to use an injector inside the body like so
#Singleton
class MongoRemainingAllowanceRepository extends RemainingAllowanceRepository{
val injector = Guice.createInjector(new MongoModule)
val mongoFactory = injector.getInstance(classOf[TMongoFactory])
val context = mongoFactory.collection("remainingAllowance")
def save(remainingAllowance: RemainingAllowance): Unit ={
context.save(RemainingAllowance.convertToMongoObject(remainingAllowance))
}
This feels like it should work and it compiles fine, but then on test or run it throws an error
Could not find a suitable constructor in play.api.Configuration. Classes
must have either one (and only one) constructor annotated with #Inject
or a zero-argument constructor that is not private. at
play.api.Configuration.class(Configuration.scala:173) while locating
play.api.Configuration
Apologies for the long post but i feel i needed to include most of this.
Does anyone know why this happens on an injector? Do i need to bind the configuration manually also now im referencing the custom module?
Any help appreciated
Thanks
Jack
When you create your class you can pass in the configuration yourself. Say you need key apiKey and its value...
val sampleConfig = Map("apiKey" ->"abcd1234")
val mongoFactory = new MongoFactory(Configuration.from(sampleConfig))
I have the following multi-module project structure built using sbt:
myProject-api
myProject-core
myProject-core is organized as below:
It contains certain actors which acts as a facade to my services. For example., I have a UserActor that sits in front of a UserService. A NotificationActor that sits in front of a NotificationService and so on.
I have another trait that exposes there actors to anybody that is interested:
trait MyProjectCoreActors {
def myAppCfg = MyProjConfig.appCfg
def userActor = myAppCfg.userActor
def notifyActor = myAppCfg.notifyActor
}
object MyProjectCoreActors {
... some initialization routing that initializes the MyProjConfig
}
My UserActor is thus defined as:
class UserActor(service: UserService) extends Actor {
...
...
}
My UserService is as follows:
class UserService(dbConfig: DbConfig) {
...
...
}
I have another class called MyProjectConfig which I initialize using the application.conf file. In this file I have the connection details to the database and so on. The MyProjectConfig is initialized as below:
trait MyProjectConfig {
def actorSystem: ActorSystem
// the actors
def userActor: ActorRef
}
object MyProjectConfig {
def apply(appConfig: Config, system: ActorSystem): MyProjectConfig = {
new MyProjectConfig {
private val dbConfig = loadDBConfig(appConfig)
override val actorSystem = system
// each actor gets its own DBConfigInstance instance
override val userActor =
actorSystem.actorOf(
Props(new UserActor(UserService(dbConfig)))
)
}
}
}
I have now the Spray routing as defined below:
trait MyProjectService extends HttpService with MyProjectCoreActors {
def pingRoute = path("ping") {
get {
userActor ! "newUser"
complete("pong!")
}
}
def pongRoute = path("pong") {
get { complete("pong!?") }
}
def route = pingRoute ~ pongRoute
}
What is now missing is a way to call the MyProjectConfig.apply(....) method and pass in the Actor System and the underlying application.conf!
This was originally a Play based application, where I had a Lifecycle plug in which had access to the underlying Application from where I got the config and the actor system. How could I now get the same here with Spray?
I have a Boot class that looks like this:
object MyBootHttpService extends App {
implicit val actorSystem = ActorSystem("myproj-actor-system")
}
How could I pass this ActorSytem to MyProjectConfig.apply(....)? and from where could I get the application.conf?
I think you can do such things (DI) in your MyBootHttpService class.
For example
object MyBootHttpService extends App {
implicit val actorSystem = ActorSystem("myproj-actor-system")
private val config = ConfigFactory.load
private val myAppConfig = MyProjectConfig(config, actorSystem)
// Initialise classes that depend on config and actorsystem....
private val service = new MyProjectService with HttpServiceActor {
override implicit val actorRefFactory = actorSystem
}
// Bind our service
IO(Http) ? Bind(listener = service, interface = "0.0.0.0", port = config.getInt("port"))
}
Typesafe config library object ConfigFactory is generally used to load config files. ConfigFactory.load with no args will try and load application.conf from the classpath.