Mockito
Mockito is a mocking framework that we found to be the most suitable for our application service and controller tests. It provided us with clean and simple API and enabled us to write very readble tests with clean verification errors.
Configuring Mockito
Add Mockito to your Play project
First thing we did was adding flyway library dependency in build.sbt
:
"org.mockito" % "mockito-all" % "1.10.+" % "test, it"
, which enabled us to use Mockito as Java API.
Second thing was addition of ScalaTest , more precisely ScalaTest Plus library for testing with mocked objects.
You can basically use any Java mocking framework with ScalaTest. ScalaTest provided us with three most popular Java mocking frameworks (JMock , EasyMock and of course Mockito). We decided to use this library mostly because of versatility of mocking frameworks and its abilty to use libraries from Scala as they are in Java. This can enable people familiar with a listed frameworks to get going quicly even in Scala.
Because we use Play Framework application , next logical step was to use ScalaTest Plus
library because this project aim was to define integration libraries between ScalaTest and other libraries , and first such integration library was (just as we would have wanted it) Play. (org.scalatestplus.play)
So , we added this library dependency too.
// ScalaTest
"org.scalatestplus" %% "play" % "1.4.0" % "test, it"
After building libraries and project we were ready for mocking and testing.
Mocking and writing Service test
ScalaTest’s MockitoSugar trait provides us with some basic syntax for Mockito. We will see in the following example how we used Mockito API to mock our objects.
We had previously defined DTO Object :
case class IngredientDTO(
ingredientId: Int,
lang: String = "en-US",
item: String
) extends Identifiable[Int] {
def id: Int = ingredientId
}
DAO trait:
trait IngredientDAO {
// some random method , not important for mocking
def findByFirstLetter(letter: Char)(implicit ec: ExecutionContext): Future[List[IngredientDTO]]
}
When we create our Service test we need to use with
keyword to declare class is using a Trait in our case a MockitoSugar via mixin.
/**
* Test IngredientService methods
*
*/
package services
import ...
import org.mockito.Mockito.when
import org.mockito.Matchers._
import org.scalatest.Matchers._
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.time._
import org.scalatest.mock.MockitoSugar
import org.scalatestplus.play.PlaySpec
...
class IngredientServiceTest extends PlaySpec with MockitoSugar with ScalaFutures {
//the test code
}
Now we can write our mock.
Using the Mockito API we could create mock with:
val mockCollaborator = mock(classOf[IngredientDAO])
but because we are using IngredientDAO trait we were able to shorten this to:
val mockIngredientDAO = mock[IngredientDAO]
next we create some ingredientDTO object
// our mocked DTO
val ingredientDTO = IngredientDTO(ingredientId = 1, lang = langCode, item = "TestIngredient")
and service:
val ingredientService = new IngredientService(mockIngredientDAO)
In a mock all methods are stubbed and return “smart return types”. This means that calling any method on a mocked class will do nothing unless you specify behaviour. So we have to “specify behavior” of all DAO methods that our test will use.
We actually call real methods with Mockito but decide what to return (our mocked values).
E.G. :
// Stub IngredientDAO methods
when(mockIngredientDAO.addIngredient(id, List(2))) thenReturn Future.successful(2)
when(mockIngredientDAO.deleteIngredient(id, List(2))) thenReturn Future.successful(2)
when(mockIngredientDAO.findById(batchId)) thenReturn Future.successful(Option(ingredientDTO))
when(mockIngredientDAO.create(ingredientDTO)) thenReturn Future.successful(id)
when(mockIngredientDAO.update(id, batchDTO)) thenReturn Future.successful(id)
when(mockIngredientDAO.delete(id)) thenReturn Future.successful(id)
After stubbing all DAO methods we are using , we defiend what will service test do:
"IngredientService" should {
"find ingredient by id" in {
val futureIngredient = ingredientService.findById(ingredientId)
whenReady(futureIngredient) { maybeIngredient =>
maybeIngredient should be(defined)
val ingredient = maybeIngredient.get
ingredient.item should equal(ingredientDTO.item)
}
}
"create new ingredient" in {
val futureIngredientOrError = for {
ingredientOrError <- ingredientService.create(ingredientDTO)
} yield ingredientOrError
whenReady(futureIngredientOrError) { ingredientModelOrError =>
ingredientModelOrError match {
case Left(errorMessage) => errorMessage.code should be(defined)
case Right(result) => {
result.item should equal(ingredientDTO.item)
result.lang should equal("en-US")
}
}
}
}
"delete ingredient" in {
val futureIngredients = for {
delete <- ingredientService.delete(id)
} yield delete
whenReady(futureIngredients) { delete =>
delete should be(Right(1234))
}
}
}
*NOTE
One thing that we noticed is that you should be careful when using methods with implicit parameters and Mockito. If you don’t explicitly specify which parameter to use, Scala will fill it with the implicit one in scope. For example, if you are passing an implicit ExecutionContext
(usually when dealing with Future’s), you could get unexpected results if you use one ExecutionContext
in your class and different one in your test. The problem is solved simply by adding the parameter explicitly, like any[ExecutionContext]
E.G.
mockIngredientDAO.findAll(any[ExecutionContext])
Mocking and writing Controller test
Since our controller has injected components we need to obtain an instance of it with the various dependencies satisfied. For this we used the GuiceApplicationBuilder
to obtain a Play application instance, and then use its injector to get an instance of our controller without having to construct it manually.
With an instance of our controller constructed, we then “apply” a mock request to our action methods and determine that they give the status and body we expect.
So, first thing we need to do when testing controllers is create ControllerTest abstract class to create a new Application for every test and bind services to mocked services
package controllers
import ...
import org.scalatest.TestData
import org.scalatest.mock.MockitoSugar
import org.scalatestplus.play.OneAppPerTest
import org.scalatestplus.play.PlaySpec
import play.api.Application
...
abstract class ControllerTest extends PlaySpec with OneAppPerTest with MockitoSugar {
/**
* Overrides newAppForTest from OneAppPerTest to create a new Application for every test
* @param testData
* @return
*/
override def newAppForTest(testData: TestData): Application = {
val mockIngredientService = mock[IngredientService]
new GuiceApplicationBuilder()
.overrides(bind[IngredientService].to(mockIngredientService)) // bind to instance
.build
}
private val app2IngredientService = Application.instanceCache[IngredientService]
def mockIngredientService = app2IngredientService(app) // public mock service to be used in tests
}
Then we created our Controller test
package controllers
import ...
import org.mockito.Matchers._
import org.mockito.Mockito._
import org.scalatest.Matchers._
...
class BatchControllerTest extends ControllerTest {
// controller test code
}
Now in place of “controller test code” we can write our mock and test.
// mock DAO
val mockIngredientDAO = mock[IngredientDAO]
// mock values
val ingredientDTO = IngredientDTO(ingredientId = 1, lang = langCode, item = "TestIngredient")
*NOTE (we also defined some other parameters , like ingredientId (int) or ingredientRoute (string), but that is not important for the concept of this blog)
Then we implement what will our controller test and what do we expect:
"Ingredient Controller" should {
"return OK when getting ingredient by ingredientId" in {
when(mockIngredientService.findById(anyString)) thenReturn Future.successful(Option(ingredientDTO))
val Some(result) = route(FakeRequest(GET, s"$ingredientRoute/$ingredientId").withHeaders("Accept-Language" -> langCode))
status(result) should be(OK)
verify(mockIngredientService).findById(ingredientId) // verify that the findById() is called with these parameters
}
"return NOT_FOUND when getting ingredient by ingredientId that doesn't exist" in {
when(mockIngredientService.findById(anyString)) thenReturn Future.successful(None)
val Some(result) = route(FakeRequest(GET, s"$ingredientRoute/$ingredientId").withHeaders("Accept-Language" -> langCode))
status(result) should be(NOT_FOUND)
verify(mockIngredientService).findById(batchId) // verify that the findById() is called
}
"return OK when creating ingredient with valid JSON" in {
val jsonIngredient = Json.toJson(ingredientDTO)
when(mockIngredientService.create(any[IngredientDTO])) thenReturn Future.successful(Right(ingredientDTO))
val Some(result) = route(FakeRequest(POST, s"$ingredientRoute").withJsonBody(jsonBatch).withHeaders("Accept-Language" -> langCode))
status(result) should be(OK)
verify(mockIngredientService).create(ingredientDTO) // verify that the create() is called with these params
}
"return OK when getting ingredient count" in {
when(mockIngredientService.count) thenReturn Future.successful(1)
val Some(result) = route(FakeRequest(GET, s"$ingredientRoute/count").withHeaders("Accept-Language" -> langCode))
status(result) should be(OK)
contentType(result) mustEqual Some("application/json")
verify(mockIngredientService).count
}
.
.
.
//etc
}
Then all we need to do is run out tests , and see the results.
Conclusion
We hope this blog will contribute your knowledge and help you with your Scala/Play project and services/controllers testing with Mockito.
Any comments, suggestions and questions are welcome.