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)
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.
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]
}
I'm having trouble writing tests for a mixin to my Play application that runs in it's own thread separate from play. I've tried over-writing WithApplication.provideApplication method with no luck. I get an inheriting conflicting methods error. (one from the real app "MyRunnableSystemWrapper", one from my mocked fake mixin called "MyMockedSystemWrapper").
execute(system) runs my system that is tested elsewhere and has sideaffects (connects to networked services, thus failing this test when such things are not available. Good news is I have a mocked service of my system wrapper that uses a system which does NOT have side affects and DB/Network calls are mocked out. However I do not know how to give THIS MOCKED version of my app to "WithApplication" test.
Reduced Code for clarity:
class Application extends Controller with MyRunnableSystemWrapper {
val pool: ExecutorService = Executors.newFixedThreadPool(1)
val system = new MyRunnableSystem() //system is abstract in MRSW ^^^ above
pool.execute(system)
def index = Action {
OK("HI")
}
}
My Test:
class MyAppTest(implicit ee: ExecutionEnv) extends Specification {
abstract class WithMyMockApp extends WithApplication {
def provideApplication = new controllers.Application with MyMockedSystemWrapper // This imports MyRunnableSystemWrapper
}
"Sending a GET request" should {
"Respond with OK" in new WithMyMockApp {
val response = route(app, FakeRequest(GET, "/")).get
status(response) mustEqual OK
}
}
}
If I'm not running my Runnable in the correct place and should be calling it somewhere else to make this testing easier, let me know!
You could inject your system wrapper instead of extending it
trait SystemWrapper {
def execute(system: RunnableSystem)
}
class MyRunnableSystemWrapper extends SystemWrapper {...}
class MyMockedSystemWrapper extends SystemWrapper {...}
class Application #Inject() (systemWrapper SystemWrapper) extends Controller {
Then you need to tell Guice which implementation of SystemWrapper you want for runtime and which one for test. One way of doing this is by using different Guice modules for runtime/test which you set in your .conf files.
I'm using Play! 2.4 with Deadbolt2 for authorization. However, since I introduced the authorization rules, I'm unable to write successful tests for my controllers. As an example:
class VisitController #Inject() (authorization: DeadboltActions) extends Controller {
def fetchDailyVisits(date: Date) = authorization.Restrict(List(Array(ADMIN_ROLE), Array(MANAGER_ROLE))) {
Action.async {
visitService.findDailyVisits(date).map(result =>
Ok(Json.toJson(result))
)
}
}
}
I'm using specs2 in the tests. My test looks like this atm:
class VisitControllerSpec extends PlaySpecification with Mockito with ScalaFutures {
val deadboltActions = mock[DeadboltActions]
"VisitControllerSpec#fetchDailyVisits" should {
val testDate = Date.from(LocalDate.of(2016, 2, 25)
.atStartOfDay(ZoneId.systemDefault()).toInstant)
"Return Status Ok with returned list" in {
val expected = List(completeVisitWithId, anotherCompleteVisitWithId)
visitService.findDailyVisits(testDate) returns Future { expected }
val request = FakeRequest(GET, "/visits?date=2016-02-25")
val result = new VisitController(deadboltActions)
.fetchDailyVisits(testDate)(request)
result.futureValue.header.status must beEqualTo(OK)
contentAsJson(result) must_== Json.toJson(expected)
}
}
}
How do I mock deadboltActions in a way I can specify the user will be allowed access?
Is there another way? Maybe by providing a different DeadboltHandler? It seems kind of obvious this would be the way to go, I just don't seem to be able to figure it out and there aren't a lot of Deadbolt2 examples out there (at least for scala).
Or, being more extreme, any other authorization framework out there that works well with scala play and allows to handle security as a cross-cutting concern without poluting the controllers? Deadbolt2 is too limited for this reason, but I honestly can't find a better authorization framework (unless I write my own).
There are a number of different ways you can do this.
If your DeadboltHandler has a DAO injected for accessing the subject, you can override the binding of the DAO to provide one containing test subjects.
abstract class AbstractControllerSpec extends PlaySpecification {
sequential
isolated
def testApp: Application = new GuiceApplicationBuilder().in(Mode.Test).bindings(bind[SubjectDao].to[TestSubjectDao]).build()
}
See the test app for an example of using this approach.
Alternatively, you can extend your DeadboltHandler implementation to override getSubject and provide a test subject from here. The binding is handled in the same way as above.
Finally, you can keep all your code as-is and populate the test database with subjects; the requests you send will be shaped by your authentication requirements (headers, something in a cookie, etc).
For unit testing, something similar applies. Given a SubjectDao that has some hard-coded subjects for test purposes, you can use WithApplication and an injector look-up to get what you need.
class TestSubjectDao extends SubjectDao {
val subjects: Map[String, Subject] = Map("greet" -> new SecuritySubject("greet",
List(SecurityRole("foo"),
SecurityRole("bar")),
List(SecurityPermission("killer.undead.zombie"))),
"lotte" -> new SecuritySubject("lotte",
List(SecurityRole("hurdy")),
List(SecurityPermission("killer.undead.vampire"))),
"steve" -> new SecuritySubject("steve",
List(SecurityRole("bar")),
List(SecurityPermission("curator.museum.insects"))),
"mani" -> new SecuritySubject("mani",
List(SecurityRole("bar"),
SecurityRole("hurdy")),
List(SecurityPermission("zombie.movie.enthusiast"))),
"trippel" -> new SecuritySubject("trippel",
List(SecurityRole("foo"),
SecurityRole("hurdy")),
List[SecurityPermission]()))
override def user(userName: String): Option[Subject] = subjects.get(userName)
}
With a controller that looks something like this:
class Subject #Inject()(deadbolt: DeadboltActions) extends Controller {
def subjectMustBePresent = deadbolt.SubjectPresent()() { authRequest =>
Future {
Ok("Content accessible")
}
}
}
We can then unit test it like this:
import be.objectify.deadbolt.scala.DeadboltActions
import be.objectify.deadbolt.scala.test.controllers.composed.Subject
import be.objectify.deadbolt.scala.test.dao.{SubjectDao, TestSubjectDao}
import play.api.Mode
import play.api.inject._
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.mvc.{Result, Results}
import play.api.test.{FakeRequest, PlaySpecification, WithApplication}
import scala.concurrent.Future
object SubjectPresentUnitSpec extends PlaySpecification with Results {
"Subject present " should {
"should result in a 401 when no subject is present" in new WithApplication(new GuiceApplicationBuilder().in(Mode.Test).bindings(bind[SubjectDao].to[TestSubjectDao]).build()) {
val deadbolt: DeadboltActions = implicitApp.injector.instanceOf[DeadboltActions]
val controller = new Subject(deadbolt)
val result: Future[Result] = call(controller.subjectMustBePresent(), FakeRequest())
val statusCode: Int = status(result)
statusCode must be equalTo 401
}
"should result in a 200 when a subject is present" in new WithApplication(new GuiceApplicationBuilder().in(Mode.Test).bindings(bind[SubjectDao].to[TestSubjectDao]).build()) {
val deadbolt: DeadboltActions = implicitApp.injector.instanceOf[DeadboltActions]
val controller = new Subject(deadbolt)
val result: Future[Result] = call(controller.subjectMustBePresent(), FakeRequest().withHeaders(("x-deadbolt-test-user", "greet")))
val statusCode: Int = status(result)
statusCode must be equalTo 200
}
}
}
It doesn't answer exactly to my original question, which was mostly related with Deadbolt2, but I kept getting frustrated with the fact I had to specify my authorization rules in my controllers, which is not truly cross cutting.
The answer provided by Steve Chaloner helps, but still forced me to go through a few hoops.
Enter Panoptes. This authorization framework is based on Filters instead of Action chaining, so it allows to easily specify authorization rules in a central location and outside of the controllers.
Setting your security rules in Panoptes is somewhat similar to Spring Security and it looks like this:
class BasicAuthHandler extends AuthorizationHandler {
override def config: Set[(Pattern, _ <: AuthorizationRule)] = {
Set(
Pattern(Some(POST), "/products") -> atLeastOne(withRole("Admin"), withRole("Manager"))
Pattern(Some(GET), "/cart[/A-Za-z0-9]*") -> withRole("Admin"),
Pattern(None, "/orders[/A-Za-z0-9]*") -> withRole("Admin")
)
}
}
Other than that, you need a couple of lines to declare the filter and plug in your AuthorizationHandler.
class Filters #Inject()(securityFilter: SecurityFilter) extends HttpFilters {
override def filters = Seq(securityFilter)
}
class ControllerProviderModule extends AbstractModule {
override def configure(): Unit = { bind(classOf[AuthorizationHandler]).to(classOf[MyAuthorizationHandler])
}
}
The README file in the git repository has more details and code samples.
It's also customizable to the point it allows to create your own AuthorizationRules. In my project I have a requirement where I need to check the mobile device that makes the call is registered in the system. I can write an AuthorizationRule to handle this for me for every request whose path matches my pattern.
Unit testing controllers is extra simple, because any mocking of the security layer can be ommited. They can be tested like any other class.
If you're having similar issues or also believe authorization rules don't belong in the controllers, have a go at Panoptes, it might suit your needs. Hope this helps someone else.
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.