I am trying to test some code using Mockito.spy
to provide behavior to some of the functions I need to provide. Although I understand that there may be better alternatives to what I am doing, I am curious why the following code throws a NullPointerException
in Scala and how could it be solved.
The class Foo
is being tested. It contains a single method act
, which for the purpose of this question just calculate the sum of numbers. Class FooSpec
is the respective unit test for class Foo
. Class Bar
is another class that is used as an argument to class Foo
to demonstrate the case that the exception arises.
The following code works. Method Foo.act
takes in no arguments.
package foo
import org.mockito.Matchers.any
import org.mockito.Mockito.{spy, times, verify, when}
import org.scalatest.WordSpec
import org.scalatest.mockito.MockitoSugar
class Foo {
def act(): Int = {
(1 to 10).sum
}
}
class FooSpec extends WordSpec with MockitoSugar {
"a test" in {
val spiedFoo = spy(new Foo)
when(spiedFoo.act()).thenReturn(100)
val result = spiedFoo.act()
assert(result == 100)
verify(spiedFoo, times(1)).act()
}
}
The following code breaks. Method Foo.act
takes in 1 argument of type Bar
. It throws a java.lang.NullPointerException
for some reason
package foo
import org.mockito.Matchers.any
import org.mockito.Mockito.{spy, times, verify, when}
import org.scalatest.WordSpec
import org.scalatest.mockito.MockitoSugar
case class Bar(value: Int)
class Foo {
def act(bar: Bar): Int = {
(1 to bar.value).sum
}
}
class FooSpec extends WordSpec with MockitoSugar {
"a test" in {
val spiedFoo = spy(new Foo)
when(spiedFoo.act(any[Bar])).thenReturn(100)
val aBar = Bar(10)
val result = spiedFoo.act(aBar)
assert(result == 100)
verify(spiedFoo, times(1)).act(any[Bar])
}
}
Here is the error message obtained from the test:
[info] FooSpec:
[info] - a test *** FAILED ***
[info] java.lang.NullPointerException:
[info] at foo.Foo.act(FooSpec.scala:12)
[info] at foo.FooSpec$$anonfun$1.apply$mcI$sp(FooSpec.scala:19)
[info] at foo.FooSpec$$anonfun$1.apply(FooSpec.scala:17)
[info] at foo.FooSpec$$anonfun$1.apply(FooSpec.scala:17)
[info] at org.scalatest.OutcomeOf$class.outcomeOf(OutcomeOf.scala:85)
[info] at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
[info] at org.scalatest.Transformer.apply(Transformer.scala:22)
[info] at org.scalatest.Transformer.apply(Transformer.scala:20)
[info] at org.scalatest.WordSpecLike$$anon$1.apply(WordSpecLike.scala:1078)
[info] at org.scalatest.TestSuite$class.withFixture(TestSuite.scala:196)
[info] ...
[info] Run completed in 761 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
[info] *** 1 TEST FAILED ***
[error] Failed tests:
[error] foo.FooSpec
[error] (foo/test:testOnly) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 6 s, completed Oct 15, 2019 5:50:20 PM
Important gotcha on spying real objects states
Sometimes it's impossible or impractical to use
when(Object)
for stubbing spies. Therefore when using spies please considerdoReturn|Answer|Throw()
family of methods for stubbing.
Hence try
doReturn(100).when(spiedFoo).act(any[Bar])
instead of
when(spiedFoo.act(any[Bar])).thenReturn(100)
Also consider moving to mockito-scala, for example
class FooSpec extends WordSpec with Matchers with IdiomaticMockito with ArgumentMatchersSugar {
"a test" in {
val spiedFoo = spy(new Foo)
100 willBe returned by spiedFoo.act(*)
val aBar = Bar(10)
val result = spiedFoo.act(aBar)
result shouldEqual 100
spiedFoo.act(*) was called
}
}