Don Roby
Don Roby

Reputation: 41145

Scala, Specs2, Mockito and null return values

I'm trying to test-drive some Scala code using Specs2 and Mockito. I'm relatively new to all three, and having difficulty with the mocked methods returning null.

In the following (transcribed with some name changes)

  "My Component's process(File)" should  {

    "pass file to Parser" in new modules {
      val file = mock[File]
      myComponent.process(file)

      there was one(mockParser).parse(file)
    }

    "pass parse result to Translator" in new modules {
      val file = mock[File]
      val myType1 = mock[MyType1]

      mockParser.parse(file) returns (Some(myType1))
      myComponent.process(file)

      there was one(mockTranslator).translate(myType1)
    }

  }

The "pass file to Parser" works until I add the translator call in the SUT, and then dies because the mockParser.parse method has returned a null, which the translator code can't take.

Similarly, the "pass parse result to Translator" passes until I try to use the translation result in the SUT.

The real code for both of these methods can never return null, but I don't know how to tell Mockito to make the expectations return usable results.

I can of course work around this by putting null checks in the SUT, but I'd rather not, as I'm making sure to never return nulls and instead using Option, None and Some.

Pointers to a good Scala/Specs2/Mockito tutorial would be wonderful, as would a simple example of how to change a line like

there was one(mockParser).parse(file)

to make it return something that allows continued execution in the SUT when it doesn't deal with nulls.

Flailing about trying to figure this out, I have tried changing that line to

there was one(mockParser).parse(file) returns myResult

with a value for myResult that is of the type I want returned. That gave me a compile error as it expects to find a MatchResult there rather than my return type.

If it matters, I'm using Scala 2.9.0.

Upvotes: 1

Views: 6741

Answers (3)

Nicolas
Nicolas

Reputation: 24769

If you don't have seen it, you can look the mock expectation page of the specs2 documentation.

In your code, the stub should be mockParser.parse(file) returns myResult

Edited after Don's edit:

There was a misunderstanding. The way you do it in your second example is the good one and you should do exactly the same in the first test:

val file = mock[File]
val myType1 = mock[MyType1]

mockParser.parse(file) returns (Some(myType1))
myComponent.process(file)
there was one(mockParser).parse(file)

The idea of unit testing with mock is always the same: explain how your mocks work (stubbing), execute, verify.

That should answer the question, now a personal advice:

Most of the time, except if you want to verify some algorithmic behavior (stop on first success, process a list in reverse order) you should not test expectation in your unit tests.

In your example, the process method should "translate things", thus your unit tests should focus on it: mock your parsers and translators, stub them and only check the result of the whole process. It's less fine grain but the goal of a unit test is not to check every step of a method. If you want to change the implementation, you should not have to modify a bunch of unit tests that verify each line of the method.

Upvotes: 4

Don Roby
Don Roby

Reputation: 41145

I have managed to solve this, though there may be a better solution, so I'm going to post my own answer, but not accept it immediately.

What I needed to do was supply a sensible default return value for the mock, in the form of an org.mockito.stubbing.Answer<T> with T being the return type.

I was able to do this with the following mock setup:

val defaultParseResult = new Answer[Option[MyType1]] {
  def answer(p1: InvocationOnMock): Option[MyType1] = None
}
val mockParser = org.mockito.Mockito.mock(implicitly[ClassManifest[Parser]].erasure,
                         defaultParseResult).asInstanceOf[Parser]

after a bit of browsing of the source for the org.specs2.mock.Mockito trait and things it calls.

And now, instead of returning null, the parse returns None when not stubbed (including when it's expected as in the first test), which allows the test to pass with this value being used in the code under test.

I will likely make a test support method hiding the mess in the mockParser assignment, and letting me do the same for various return types, as I'm going to need the same capability with several return types just in this set of tests.

I couldn't locate support for a shorter way of doing this in org.specs2.mock.Mockito, but perhaps this will inspire Eric to add such. Nice to have the author in the conversation...

Edit

On further perusal of source, it occurred to me that I should be able to just call the method

def mock[T, A](implicit m: ClassManifest[T], a: org.mockito.stubbing.Answer[A]): T = org.mockito.Mockito.mock(implicitly[ClassManifest[T]].erasure, a).asInstanceOf[T]

defined in org.specs2.mock.MockitoMocker, which was in fact the inspiration for my solution above. But I can't figure out the call. mock is rather overloaded, and all my attempts seem to end up invoking a different version and not liking my parameters.

So it looks like Eric has already included support for this, but I don't understand how to get to it.

Update

I have defined a trait containing the following:

  def mock[T, A](implicit m: ClassManifest[T], default: A): T = {
    org.mockito.Mockito.mock(
      implicitly[ClassManifest[T]].erasure,
      new Answer[A] {
      def answer(p1: InvocationOnMock): A = default
    }).asInstanceOf[T]
  }

and now by using that trait I can setup my mock as

implicit val defaultParseResult = None
val mockParser = mock[Parser,Option[MyType1]]

I don't after all need more usages of this in this particular test, as supplying a usable value for this makes all my tests work without null checks in the code under test. But it might be needed in other tests.

I'd still be interested in how to handle this issue without adding this trait.

Upvotes: 1

Eric
Eric

Reputation: 15557

Without the full it's difficult to say but can you please check that the method you're trying to mock is not a final method? Because in that case Mockito won't be able to mock it and will return null.

Another piece of advice, when something doesn't work, is to rewrite the code with Mockito in a standard JUnit test. Then, if it fails, your question might be best answered by someone on the Mockito mailing list.

Upvotes: 0

Related Questions