Search code examples
scalanullpointerexceptionmockitoscalatest

Using Mockito to spy a method with object argument results in NullPointerException


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

Solution

  • 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 consider doReturn|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
      }
    }