finatra/examples/twitter-clone: Testing the firebase client - scala

In the twitter-clone example, the following firebase client is defined:
#Singleton
class FirebaseClient #Inject()(
httpClient: HttpClient,
mapper: FinatraObjectMapper) {
// ...
}
I would like to write a test that uses this class. The problem I have is that I cannot simply instanciate a variable of this class in my test code:
class FirebaseClientTest extends ??? {
val firebaseClient: FirebaseClient = new FirebaseClient(???, ???)
}
Since I don't know how an instance of HttpClient and FinatraObjectMapper is actually created in the production code. I could try to create these objects manually, but this adds boilerplate which I'd like to avoid.
How can I get an instance of FirebaseClient by the magic of the dependecy injection mechanisms used in Finatra?

The answer to this question is detailed in the finatra-users group. I'm summarizing it here for the sake of completeness.
Basically instances that require dependencies to be injected can be obtained by using the TestInjector class. For the problem at hand, a FirebaseClient instance can be obtained as follows:
class FirebaseClientTest extends SomeClassOfATestFramework {
val injector = TestInjector(FirebaseClientModule)
val firebaseClient: FirebaseClient = injector.instance[FirebaseClient]
}

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

Guice bind a class and its adapter

I would like implement a Guice module which binds an adapter to a named argument, but to create this adapter, it needs to instantiate another class, which also need injected arguments.
Here is the example in Scala:
trait Service
class UserService #Inject()(#Named(value = "foo") foo: String) extends Service
trait Adapter
class AdapterImpl(service: Service) extends Adapter
class AdapterRef(val adapter: Adapter)
class Module extends AbstractModule {
override def configure(): Unit = {
val fooValue = "bar"
bind(classOf[String])
.annotatedWith(Names.named("foo"))
.toInstance(fooValue)
val userService = new UserService(fooValue) //It should be instantiated by Guice somehow
bind(classOf[AdapterRef])
.annotatedWith(Names.named("userService"))
.toInstance(new AdapterRef(new AdapterImpl(userService))) //Thats kinda ok
}
}
Can someone point me to the right direction?
Thank you,
Gabor
You can use a Provides method within your module, which lets you remove the binding. This is my best effort at Scala so if the syntax is incorrect let me know.
#Provides() #Singleton() def provideAdapterRef(service: Service): AdapterRef = {
return new AdapterRef(new AdapterImpl(service))
}
Note the use of Singleton to imitate your examples use of toInstance. If you don't need it to always provide the same instance I would recommend removing the scope and letting it create a new one every time.
An alternative solution is to use a Provider. This requires you to keep a modified version of the binding in your module and create an extra class, but it can be a cleaner solution if your Provider is more complex.
//Can inject UserService as well, or provide annotations to configure which
//implementation of Service you get if you have more than one.
class UserServiceProvider #Inject()(service: Service) extends Provider[AdapterRef] {
override def get(): AdapterRef {
return new AdapterRef(new AdapterImpl(service))
}
}
Then you can change your binding in the module to
bind(classOf[AdapterRef])
.annotatedWith(Names.named("userService"))
.toProvider(classOf[UserServiceProvider])
.in(classOf[Singleton])
Here note the use of in(Singleton) to replicate your toInstance behavior.

Injecting Play Application for a Slick Database Service

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.

Play/Scala injecting Object into controller for testing

I saw this thread Play/Scala injecting controller into test I have similar issue like this, but my issue is how to inject the object for testing the controller.
Controller
#Singleton
class ExampleCtrl #Inject() (dao: TestDAO) extends Controller {
//code here
def testMethod = Action { request =>
dao.exampleMethod()
Ok(Json.obj("test" -> "test")
}
}
DAO
class TestDAO #Inject()(protected val provider: DatabaseConfigProvider){
def exampleMethod()
}
Test
class ExampleCtrlSpec extends PlaySpec with MockitoSugar {
val service = mock[TestDAO]//problem on injecting DatabaseConfigProvider
val controller = new ExampleCtrl(service)
//service has null value for DatabaseConfigProvider properties
"testMethod()" should {
"return JSON" in {
when(service.exampleMethod) thenReturn "json data"
val result: Future[Result] =
controller.testMethod().apply(FakeRequest())
.withJsonBody(JSON.json("""[{"test":"test"}]"""))
contentAsString(result) mustEqual """[{"test":"test"}]"""
}
}
}
So I tried to reproduce the issue, but after fixing some issues that the IDE identified with the code (e.g. brackets missing), the test is passing.
problem on injecting DatabaseConfigProvider
I don't see any problem here, as the code is passing. From the coder's point of view, mock[TestDAO] doesn't actually instantiate a real TestDAO, but it creates something that looks like one (interface-wise), but doesn't actually contain any of the logic you wrote inside TestDAO. Therefore, the mock object also doesn't need the DatabaseConfigProvider to be injected.
service has null value for DatabaseConfigProvider properties
Because the service (your mock TestDAO) is a mock, this isn't a problem, since no logic will be using it. The only logic that your mock actually executes is here:
when(service.exampleMethod) thenReturn "json data"
When using mocks, you need to code the behaviour you want them to exhibit in the test, as you've done in the snippet above.
If you want to run any of the DatabaseConfigProvider methods, perhaps you need to:
create one directly (e.g. val myProvider = [new] DatabaseConfigProvider(...)),
move the mocking one level out, so that you have a real controller, a real TestDAO and a mock DatabaseConfigProvider (something like val controller = new ExampleCtrl(new TestDAO(mock[DatabaseConfigProvider]))), or
write a different kind of test all together.
I solved the problem on the following way.
def testDAO (implicit app: Application) = {
val app2testDAO = Application.instanceCache[TestDAO ]
app2testDAO(app)
}
val controller = new ExampleCtrl(testDAO)

Using Mockito in Scala with Spec2

This is in the context of Play framework. I have a controller that instantiates a Validator. Validator has validate method. Controller has a method putEnity() that validates the payload it receives using this validate().
For unit testing the controller, i would like to mock the call Validator.validate(). TestController looks like this
class EntityControllerTest extends FlatSpec with Mockito {
def testPutEntity() = {
val payload = createPayload()
val mockValidator = mock[Validator]
when(mockValidator.validate(anyString, anyString)).thenReturn(EntityValidationResult(true, "Test"))
EntityController.putEntity(payload)
}
The problem is that, this mock call is not being used but the actual validate() is called and hence the test fails.
How can i fix this
The problem is that in your final line:
EntityController.putEntity(payload)
you are effectively calling a static method on EntityController, and despite all your attempts to configure a mock Validator you've never had an opportunity to "inject it" into the controller.
It's going to be a little bit difficult to suggest an ideal solution without seeing how you've implemented your controller, but at a guess, you'd want to do something like the following to allow a mocked Validator to be injected for testing, but for everything else to work like before.
I'll step through it to (hopefully) make it clearer:
Step 1 - make EntityController instantiable:
You've probably got something like this:
object EntityController extends Controller {
...
def putEntity = Action ...
..
}
Replace it with this:
class EntityControl extends Controller {
...
def putEntity = Action ...
...
}
object EntityController extends EntityControl
Now you've given yourself the ability to new something with the same behaviour as your "production" object. But we need to be able to substitute in a mock validator...
Step 2 - require a validator instance in your EntityControl:
Your old EntityController object probably had something like this:
object EntityController extends Controller {
val validator = new Validator(...)
...
}
which is why you could never get your mocked validator involved. Let's inject it as a constructor parameter so it can't be forgotten:
class EntityControl(val validator:Validator) extends Controller {
...
}
object EntityController extends EntityControl(new Validator(...))
So again, our "production" EntityController object has all the functionality we used to have, but the key difference is that we've exposed a "testing seam" to allow a mock to be injected when needed.
Step 3 - inject, and test!
Flip to your test spec, and set up a testable EntityControl instance
class EntityControllerTest extends FlatSpec with Mockito {
def testPutEntity() = {
val payload = createPayload()
val mockValidator = mock[Validator]
when(mockValidator.validate(anyString, anyString)).thenReturn(EntityValidationResult(true, "Test"))
val myTestableEC = new EntityControl(mockValidator)
myTestableEC.putEntity(payload)
}
As you develop more test cases, you'll probably want to extract the setup-and-wiring parts of the test into a suitable function, or even a Specs2 Scope.
Hope this helps, and clarifies a few effective unit testing ideas for you too.