ScalaTest, Mockito, Guice and PartialMocking - scala

I am using Google Guice as DI framework and I am writing Unit Tests for my classes which use Google Guice. I am also trying to do partial mocking.
Here is the code I wrote
class Test1 {
def test1() = "I do test1"
}
class Test2 {
def test2() = "I do test2"
}
class TestPartialMock #Inject()(t1: Test1, t2: Test2) {
def test3() = "I do test3"
def createList() : List[String] = List(t1.test1(), t2.test2(), test3())
}
My objective is to write a test case for the code above, but I ONLY want to mock test3
I wrote this test case
class TestModule extends AbstractModule with ScalaModule with MockitoSugar {
override def configure() = {
bind[Test1]
bind[Test2]
val x = mock[TestPartialMock]
when(x.test3()).thenReturn("I am mocked")
when(x.createList()).thenCallRealMethod()
bind(classOf[TestPartialMock]).toInstance(x)
}
}
class PartialMockTest extends FunSpec with Matchers {
describe("we are testing workhorse but mock test3") {
it("should return mock for test3") {
val module = new TestModule
val injector = Guice.createInjector(module)
val tpm = injector.getInstance(classOf[TestPartialMock])
val result = tpm.workHorse()
result should contain ("I do test2")
result should contain ("I do test1")
result should contain ("I am mocked")
result should not contain ("I do test3")
}
}
}
However the test fails with a null pointer exception on the dependencies (call to t1)
java.lang.NullPointerException
at TestPartialMock.createList(TestPartialMock.scala:9)
at PartialMockTest.$anonfun$new$2(PartialMockTest.scala:16)
at org.scalatest.OutcomeOf.outcomeOf(OutcomeOf.scala:85)
at org.scalatest.OutcomeOf.outcomeOf$(OutcomeOf.scala:83)
at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
at org.scalatest.Transformer.apply(Transformer.scala:22)
at org.scalatest.Transformer.apply(Transformer.scala:20)
at org.scalatest.FunSpecLike$$anon$1.apply(FunSpecLike.scala:454)
So how can I have the injected dependencies along with the mocking for test3 method?
Here are my dependencies if you need to look at those
"net.codingwell" %% "scala-guice" % "4.1.0",
"org.scalatest" % "scalatest_2.12" % "3.0.3",
"org.scalamock" % "scalamock-scalatest-support_2.12" % "3.5.0",
"org.mockito" % "mockito-core" % "2.7.22"

The trick is that Mockito doesn't call the constructor of the (base) class when a mocked object is created. Thus the dependencies of the TestPartialMock are not initialized. The simplest way to work this around is to spy on a real object that you can create with whatever configuration you want
class TestModule extends AbstractModule with ScalaModule with MockitoSugar {
override def configure() = {
//bind[Test1]
//bind[Test2]
//val x = mock[TestPartialMock]
val realObject = new TestPartialMock(new Test1, new Test2)
val x = spy(realObject)
when(x.test3()).thenReturn("I am mocked")
when(x.createList()).thenCallRealMethod()
bind(classOf[TestPartialMock]).toInstance(x)
}
}

Related

Unit test LazyLogging using Mockito

I am having a class which extends LazyLogging trait
class TaskProcessor()
extends Processor
with LazyLogging {
def a1() = {
logger.info("Test logging")
}
}
Now, I want to test whether my logging works. So I followed this example Unit test logger messages using specs2 + scalalogging and wrote my test as follows
"TaskProcessor" should "test logging" in {
val mockLogger = mock[Logger]
val testable = new TaskProcessor {
override val logger: Logger = mockLogger
}
verify(mockLogger).info("Test logging")
}
I get the following error
Error:(32, 20) overriding lazy value logger in trait LazyLogging of type com.typesafe.scalalogging.Logger;
value logger must be declared lazy to override a concrete lazy value
override val logger: Logger = mockLogger
To resolve this, I modify statement
override val logger: Logger = mockLogger
to
override lazy val logger: Logger = mockLogger
I get the following error
Cannot mock/spy class com.typesafe.scalalogging.Logger
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.typesafe.scalalogging.Logger
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
at org.scalatest.mockito.MockitoSugar.mock(MockitoSugar.scala:73)
at org.scalatest.mockito.MockitoSugar.mock$(MockitoSugar.scala:72)
My dependecies are as follows
"org.scalatest" %% "scalatest" % "3.0.5" % "test",
"org.mockito" % "mockito-all" % "1.10.19" % Test,
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
Can anyone please guide me as to how I can mock the logger and do the testing.
The problem is that com.typesafe.scalalogging.Logger class cannot be mocked because it's final, but we still can mock underlying org.slf4j.Logger.
import org.scalatest.mockito.MockitoSugar
import org.slf4j.{Logger => UnderlyingLogger}
import com.typesafe.scalalogging.Logger
import org.scalatest.{Matchers, WordSpec, FlatSpec}
import org.mockito.Mockito._
class TaskProcessorSpec extends FlatSpec with Matchers with MockitoSugar {
"TaskProcessor" should "test logging" in {
val mockLogger = mock[UnderlyingLogger]
when(mockLogger.isInfoEnabled).thenReturn(true)
val testable = new TaskProcessor {
override lazy val logger = Logger(mockLogger)
}
testable.a1()
verify(mockLogger).info("Test logging")
}
}

Using scalamock outside of MockFactory

I want to add integration tests to my project using cucumber feature files. I have got this working using this project as an example: https://github.com/jecklgamis/cucumber-jvm-scala-example
The problem I am running into is when I want to mock some objects. ScalaMock and EasyMock all seem to need scalatest or something similar.
My build.sbt has these lines:
libraryDependencies ++= Seq(
"io.cucumber" %% "cucumber-scala" % "2.0.1" % Test,
"io.cucumber" % "cucumber-junit" % "2.0.1" % Test,
"org.scalamock" %% "scalamock" % "4.0.0" % Test,
"org.scalatest" %% "scalatest" % "3.0.1" % Test,
etc..
My stepdef file has this:
import com.typesafe.config.{Config, ConfigFactory}
import cucumber.api.scala.{EN, ScalaDsl}
import eu.xeli.jpigpio.JPigpio
class StepDefs extends ScalaDsl with EN {
var config: Config = null
var jpigpio: JPigpio = null
Given("""^an instance of pigpio$""") { () =>
jpigpio = mock[JPigpio]
}
}
The mock[JPigpio] call gives a symbol not found error. I assume because this class does not extend MockFactory.
How can I use scalamock outside of an MockFactory class?
Bit of a quick and dirty example that does not pull in Scalatest, but I'm sure you can piece the rest together. I'd actually be curious to see this working with Cucumber :)
import org.scalamock.MockFactoryBase
import org.scalamock.clazz.Mock
object NoScalaTestExample extends Mock {
trait Cat {
def meow(): Unit
def isHungry: Boolean
}
class MyMockFactoryBase extends MockFactoryBase {
override type ExpectationException = Exception
override protected def newExpectationException(message: String, methodName: Option[Symbol]): Exception =
throw new Exception(s"$message, $methodName")
def verifyAll(): Unit = withExpectations(() => ())
}
implicit var mc: MyMockFactoryBase = _
var cat: Cat = _
def main(args: Array[String]): Unit = {
// given: I have a mock context
mc = new MyMockFactoryBase
// and am mocking a cat
cat = mc.mock[Cat]
// and the cat meows
cat.meow _ expects() once()
// and the cat is always hungry
cat.isHungry _ expects() returning true anyNumberOfTimes()
// then the cat needs feeding
assert(cat.isHungry)
// and the mock verifies
mc.verifyAll()
}
}
This will actually throw as the meows expectation is not satisfied (just to demo)
Exception in thread "main" java.lang.Exception: Unsatisfied expectation:
Expected:
inAnyOrder {
<mock-1> Cat.meow() once (never called - UNSATISFIED)
<mock-1> Cat.isHungry() any number of times (called once)
}
Actual:
<mock-1> Cat.isHungry(), None
at NoScalaTestExample$MyMockFactoryBase.newExpectationException(NoScalaTestExample.scala:13)
at NoScalaTestExample$MyMockFactoryBase.newExpectationException(NoScalaTestExample.scala:10)
at org.scalamock.context.MockContext$class.reportUnsatisfiedExpectation(MockContext.scala:45)
at NoScalaTestExample$MyMockFactoryBase.reportUnsatisfiedExpectation(NoScalaTestExample.scala:10)

Specs2: how to test a class with more than one injected dependency?

Play 2.4 app, using dependency injection for service classes.
I found that Specs2 chokes when a service class being tested has more than one injected dependency. It fails with "Can't find a constructor for class ..."
$ test-only services.ReportServiceSpec
[error] Can't find a constructor for class services.ReportService
[error] Error: Total 1, Failed 0, Errors 1, Passed 0
[error] Error during tests:
[error] services.ReportServiceSpec
[error] (test:testOnly) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 2 s, completed Dec 8, 2015 5:24:34 PM
Production code, stripped to bare minimum to reproduce this problem:
package services
import javax.inject.Inject
class ReportService #Inject()(userService: UserService, supportService: SupportService) {
// ...
}
class UserService {
// ...
}
class SupportService {
// ...
}
Test code:
package services
import javax.inject.Inject
import org.specs2.mutable.Specification
class ReportServiceSpec #Inject()(service: ReportService) extends Specification {
"ReportService" should {
"Work" in {
1 mustEqual 1
}
}
}
If I remove either UserService or SupportService dependency from ReportService, the test works. But obviously the dependencies are in the production code for a reason. Question is, how do I make this test work?
Edit: When trying to run the test inside IntelliJ IDEA, the same thing fails, but with different messages: "Test framework quit unexpectedly", "This looks like a specs2 exception..."; see full output with stacktrace. I opened a Specs2 issue as instructed in the output, though I have no idea if the problem is in Play or Specs2 or somewhere else.
My library dependencies below. (I tried specifying Specs2 version explicitly, but that didn't help. Looks like I need specs2 % Test as is, for Play's test classes like WithApplication to work.)
resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases"
libraryDependencies ++= Seq(
specs2 % Test,
jdbc,
evolutions,
filters,
"com.typesafe.play" %% "anorm" % "2.4.0",
"org.postgresql" % "postgresql" % "9.4-1205-jdbc42"
)
There is limited support for dependency injection in specs2, mostly for execution environments or command-line arguments.
There is nothing preventing you from just using a lazy val and your favourite injection framework:
class MySpec extends Specification with Inject {
lazy val reportService = inject[ReportService]
...
}
With Play and Guice, you could have a test helper such as this:
import play.api.inject.guice.GuiceApplicationBuilder
import scala.reflect.ClassTag
trait Inject {
lazy val injector = (new GuiceApplicationBuilder).injector()
def inject[T : ClassTag]: T = injector.instanceOf[T]
}
If you really need runtime dependency injection, then it's better to use Guice loading, I guess:
package services
import org.specs2.mutable.Specification
import scala.reflect.ClassTag
import com.google.inject.Guice
// Something you'd like to share between your tests
// or maybe not
object Inject {
lazy val injector = Guice.createInjector()
def apply[T <: AnyRef](implicit m: ClassTag[T]): T =
injector.getInstance(m.runtimeClass).asInstanceOf[T]
}
class ReportServiceSpec extends Specification {
lazy val reportService: ReportService = Inject[ReportService]
"ReportService" should {
"Work" in {
reportService.foo mustEqual 2
}
}
}
Alternatively you can implement Inject object as
import scala.reflect.ClassTag
import play.api.inject.guice.GuiceApplicationBuilder
object Inject {
lazy val injector = (new GuiceApplicationBuilder).injector()
def apply[T : ClassTag]: T = injector.instanceOf[T]
}
It depends whether you want to use Guice directly, or thru play wrappers.
Looks like you are out of luck ATM: The comment says
Try to create an instance of a given class by using whatever constructor is available and trying to instantiate the first parameter recursively if there is a parameter for that constructor.
val constructors = klass.getDeclaredConstructors.toList.filter(_.getParameterTypes.size <= 1).sortBy(_.getParameterTypes.size)
i.e. Specs2 doesn't provide own DI out-of-the box,
Or you can reimplement the functionality yourself, if Guice isn't working for you.
App code:
package services
import javax.inject.Inject
class ReportService #Inject()(userService: UserService, supportService: SupportService) {
val foo: Int = userService.foo + supportService.foo
}
class UserService {
val foo: Int = 1
}
class SupportService {
val foo: Int = 41
}
Test code
package services
import org.specs2.mutable.Specification
import scala.reflect.ClassTag
import java.lang.reflect.Constructor
class Trick {
val m: ClassTag[ReportService] = implicitly
val classLoader: ClassLoader = m.runtimeClass.getClassLoader
val trick: ReportService = Trick.createInstance[ReportService](m.runtimeClass, classLoader)
}
object Trick {
def createInstance[T <: AnyRef](klass: Class[_], loader: ClassLoader)(implicit m: ClassTag[T]): T = {
val constructors = klass.getDeclaredConstructors.toList.sortBy(_.getParameterTypes.size)
val constructor = constructors.head
createInstanceForConstructor(klass, constructor, loader)
}
private def createInstanceForConstructor[T <: AnyRef : ClassTag]
(c: Class[_], constructor: Constructor[_], loader: ClassLoader): T = {
constructor.setAccessible(true)
// This can be implemented generically, but I don't remember how to deal with variadic functions
// generically. IIRC even more reflection.
if (constructor.getParameterTypes.isEmpty)
constructor.newInstance().asInstanceOf[T]
else if (constructor.getParameterTypes.size == 1) {
// not implemented
null.asInstanceOf[T]
} else if (constructor.getParameterTypes.size == 2) {
val types = constructor.getParameterTypes.toSeq
val param1 = createInstance(types(0), loader)
val param2 = createInstance(types(1), loader)
constructor.newInstance(param1, param2).asInstanceOf[T]
} else {
// not implemented
null.asInstanceOf[T]
}
}
}
// NB: no need to #Inject here. The specs2 framework does it for us.
// It sees spec with parameter, and loads it for us.
class ReportServiceSpec (trick: Trick) extends Specification {
"ReportService" should {
"Work" in {
trick.trick.foo mustEqual 2
}
}
}
And that expectedly fails with
[info] ReportService should
[error] x Work
[error] '42' is not equal to '2' (FooSpec.scala:46)
If you don't need runtime dependency injection, then it's better to use cake pattern, and forget reflection all-together.
My colleague suggested a "low-tech" workaround. In the test, instantiate service classes with new:
class ReportServiceSpec extends Specification {
val service = new ReportService(new UserService, new SupportService)
// ...
}
This also works:
class ReportServiceSpec #Inject()(userService: UserService) extends Specification {
val service = new ReportService(userService, new SupportService)
// ...
}
Feel free to post more elegant solutions. I've yet to see a simple DI solution that works (with Guice, Play's default).
Does anyone else find it curious that Play's default test framework does not play well with Play's default DI mechanism?
Edit: In the end I went with an "Injector" test helper, almost the same as what Eric suggested:
Injector:
package testhelpers
import play.api.inject.guice.GuiceApplicationBuilder
import scala.reflect.ClassTag
/**
* Provides dependency injection for test classes.
*/
object Injector {
lazy val injector = (new GuiceApplicationBuilder).injector()
def inject[T: ClassTag]: T = injector.instanceOf[T]
}
Test:
class ReportServiceSpec extends Specification {
val service = Injector.inject[ReportService]
// ...
}

Mockito Stubbing consecutive calls (iterator-style stubbing), exceptions and return void

I'm struggling to get the following test working, basically I want the mocked service call to throw an exception when first called, and work ok on the 2nd invocation. The service method returns nothing (void/Unit), written in scala
import org.mockito.{Mockito}
import org.scalatest.mock.MockitoSugar
import org.scalatest.{BeforeAndAfter, FeatureSpec}
import org.mockito.Mockito._
class MyMocksSpec extends FeatureSpec with BeforeAndAfter with MockitoSugar {
var myService: MyService = _
var myController: MyController = _
before {
myService = mock[MyService]
myController = new MyController(myService)
}
feature("a feature") {
scenario("a scenario") {
Mockito.doThrow(new RuntimeException).when(myService.sideEffect())
Mockito.doNothing().when(myService.sideEffect())
myController.showWebPage()
myController.showWebPage()
verify(myService, atLeastOnce()).sayHello("tony")
verify(myService, atLeastOnce()).sideEffect()
}
}
}
class MyService {
def sayHello(name: String) = {
"hello " + name
}
def sideEffect(): Unit = {
println("well i'm not doing much")
}
}
class MyController(myService: MyService) {
def showWebPage(): Unit = {
myService.sideEffect()
myService.sayHello("tony")
}
}
Here is the build.sbt file
name := """camel-scala"""
version := "1.0"
scalaVersion := "2.11.7"
libraryDependencies ++= {
val scalaTestVersion = "2.2.4"
Seq(
"org.scalatest" %% "scalatest" % scalaTestVersion % "test",
"org.mockito" % "mockito-all" % "1.10.19")
}
Unfinished stubbing detected here:
-> at MyMocksSpec$$anonfun$2$$anonfun$apply$mcV$sp$1.apply$mcV$sp(MyMocksSpec.scala:24)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Hints:
1. missing thenReturn()
2. you are trying to stub a final method, you naughty developer!
3: you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed
I followed the example (I think) here
Turns out the I was setting up the mock incorrectly, solution below..
Mockito.doThrow(new RuntimeException).when(myService).sideEffect()
Mockito.doNothing().when(myService).sideEffect()
instead of the incorrect
Mockito.doThrow(new RuntimeException).when(myService.sideEffect())
Mockito.doNothing().when(myService.sideEffect())

Subproject dependencies in SBT

I am having a strange problem with SBT subprojects which I think is dependency related. Here's my setup:
I have an SBT project with two subprojects A and B.
A contains a class and companion object MyA
B depends on A.
B contains an object MyB which has a main method.
When I try to execute MyB from the SBT prompt, I get a NoSuchMethodError on MyA. This is not a ClassNotFoundException, but maybe it's happening because it sees the MyA class on the classpath, but not the MyA object.
As a sanity check, I dropped the B subproject and moved its source into the A source tree. When I run MyB from the SBT prompt, it works as expected.
Has anyone run into this, or am I doing something obviously wrong?
Here is my project configuration:
class MyProject(info: ProjectInfo) extends ParentProject(info) {
lazy val a = project("a", "a", new AProject(_))
lazy val b = project("b", "b", new BProject(_), a)
object Dependencies {
lazy val scalaTest = "org.scalatest" % "scalatest_2.9.0" % "1.4.1" % "test"
}
class AProject(info: ProjectInfo) extends DefaultProject(info) with AutoCompilerPlugins {
val scalaTest = Dependencies.scalaTest
val continuationsPlugin = compilerPlugin("org.scala-lang.plugins" % "continuations" % "2.9.0")
override def compileOptions = super.compileOptions ++ compileOptions("-P:continuations:enable") ++ compileOptions("-unchecked")
}
class BProject(info: ProjectInfo) extends DefaultProject(info)
}
It turns out to have been a problem enabling the continuations plugin on project B. Here's my working configuration:
class MyProject(info: ProjectInfo) extends ParentProject(info) {
lazy val a = project("a", "a", new AProject(_))
lazy val b = project("b", "b", new BProject(_), a)
object Dependencies {
lazy val scalaTest = "org.scalatest" % "scalatest_2.9.0" % "1.4.1" % "test"
}
class AProject(info: ProjectInfo) extends DefaultProject(info) with AutoCompilerPlugins {
val scalaTest = Dependencies.scalaTest
val continuationsPlugin = compilerPlugin("org.scala-lang.plugins" % "continuations" % "2.9.0")
override def compileOptions = super.compileOptions ++ compileOptions("-P:continuations:enable") ++ compileOptions("-unchecked")
}
class BProject(info: ProjectInfo) extends DefaultProject(info) with AutoCompilerPlugins {
override def compileOptions = super.compileOptions ++ compileOptions("-P:continuations:enable") ++ compileOptions("-unchecked")
}
}