I recently added a dependency on Specs2 to a project and noticed that some existing tests written with ScalaTest and Mockito failed. These tests passed again once Specs2 was removed. Why does this happen?
lazy val scalatestandspecscoexisting = Project(
id = "scalatest-and-specs-coexisting",
base = file("."),
settings = Project.defaultSettings ++
GraphPlugin.graphSettings ++
Seq(
name := "Scalatest-And-Specs-Coexisting",
organization := "com.bifflabs",
version := "0.1",
scalaVersion := "2.9.2",
// libraryDependencies ++= Seq(scalaTest, mockito) //Tests Pass, no-specs2
libraryDependencies ++= Seq(scalaTest, specs2, mockito) //Tests Fail
)
)
The tests that failed all used Mockito and all setup a mock method with two different parameters. One of the calls to the mock does not return value it was set up with. The example below fails. A further requirement was that type must be a Function1 (or have apply method).
import org.scalatest.FunSuite
import org.scalatest.mock.MockitoSugar
import org.mockito.Mockito.when
trait MockingBird {
//Behavior only reproduces when input is Function1
def sing(input: Set[String]): String
}
class MockSuite extends FunSuite with MockitoSugar {
val iWannaRock = Set("I wanna Rock")
val rock = "Rock!"
val wereNotGonnaTakeIt = Set("We're not gonna take it")
val no = "No! We ain't gonna take it"
test("A mock should match on parameter but isn't") {
val mockMockingBird = mock[MockingBird]
when(mockMockingBird.sing(iWannaRock)).thenReturn(rock)
//Appears to return this whenever any Set is passed to sing
when(mockMockingBird.sing(wereNotGonnaTakeIt)).thenReturn(no)
// Succeeds because it was set up last
assert(mockMockingBird.sing(wereNotGonnaTakeIt) === no)
// Fails because the mock returns "No! We ain't gonna take it"
assert(mockMockingBird.sing(iWannaRock) === rock)
}
}
Output:
[info] MockSuite:
[info] - A mock should match on parameter but isn't *** FAILED ***
[info] "[No! We ain't gonna take it]" did not equal "[Rock!]" (MockSuite.scala:38)
[error] Failed: : Total 1, Failed 1, Errors 0, Passed 0, Skipped 0
EDIT - according to Eric's comment below, this is a bug in Specs2 ≤ 1.12.2. Should be fixed in 1.12.3.
It turns out that Specs2 redefines some of the behavior in Mockito in order to get by-name parameters to match.
"I don't like this, but that's the only way I found to match byname parameters: http://bit.ly/UF9bVC . You might want that."
From the Specs2 documentation
Byname parameters can be verified but this will not work if the specs2 jar is not put first on the classpath, before the mockito jar. Indeed specs2 redefines a Mockito class for intercepting method calls so that byname parameters are properly handled.
In order to get my tests to pass again, I did the opposite of what was suggested in the specs2 documentation and added Specs2 dependency after Mockito. I have not tried, but I would expect by-name parameter matching to fail.
lazy val scalatestandspecscoexisting = Project(
id = "scalatest-and-specs-coexisting",
base = file("."),
settings = Project.defaultSettings ++
GraphPlugin.graphSettings ++
Seq(
name := "Scalatest-And-Specs-Coexisting",
organization := "com.bifflabs",
version := "0.1",
scalaVersion := "2.9.2",
// libraryDependencies ++= Seq(scalaTest, mockito) //Tests Pass
libraryDependencies ++= Seq(scalaTest, mockito, specs2) //Tests Pass
// libraryDependencies ++= Seq(scalaTest, specs2, mockito) //Tests Fail
)
)
My tests now pass
[info] MockSuite:
[info] - A mock should match on parameter but isn't
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0