I have a test along these lines:
httpClient.post(anyString, anyString) returns (first, second)
//do my thing
there were two(httpClient).post(anyString, anyString)
This works fine, but I want to verify that the first call passes a different body than the second call. The body is rather big and I don't want to do precise matching on a strict example. I've tried this:
there was one(httpClientMock).postMessage(anyString, argThat(contain("FOO"))
there was one(httpClientMock).postMessage(anyString, argThat(contain("FOO"))
That makes Mockito complain:
InvalidUseOfMatchersException:
[error] Invalid use of argument matchers!
[error] 2 matchers expected, 3 recorded:
I've also tried:
there was one(httpClientMock).postMessage(argThat(contain("foo")), argThat(contain("FOO")))
there was one(httpClientMock).postMessage(argThat(contain("foo")), argThat(contain("FOO")))
which results in:
Wanted 1 time:
[error] -> ...
[error] But was 2 times. Undesired invocation: ...
It seems to me that something like this should be possible, but I can't seem to figure it out. Insights?
I think that this is more of a problem with Mockito to begin with. When you're using Mockito with specs2 and you're in doubt, always drop down to the direct Mockito API:
// simplified httpClient with only one parameter
val httpClient = mock[HttpClient]
httpClient.post(anyString) returns ""
httpClient.post("s1")
httpClient.post("s2")
// forget specs2
// there was two(httpClient).post(anyString)
org.mockito.Mockito.verify(httpClient, org.mockito.Mockito.times(1)).post("s1")
// I guess that you don't want this to pass but it does
org.mockito.Mockito.verify(httpClient, org.mockito.Mockito.times(1)).post("s1")
One possible way around this is to define a matcher that will check the successive values of an argument:
there was two(httpClient).post(consecutiveValues(===("s1"), ===("s2")))
And the consecutiveValues
matcher is defined as such:
import matcher._
import MatcherImplicits._
// return a matcher that will check a value against a different
// `expected` matcher each time it is invoked
def consecutiveValues[T](expected: Matcher[T]*): Matcher[T] = {
// count the number of tested values
var i = -1
// store the results
var results: Seq[(Int, MatchResult[T])] = Seq()
def result(t: T) = {
i += 1
// with Mockito values are tested twice
// the first time we return the result (but also store it)
// the second time we return the computed result
if (i < expected.size) {
val mr = expected(i).apply(Expectable(t))
results = results :+ (i, mr)
mr
} else results(i - expected.size)._2
}
// return a function that is translated to a specs2 matcher
// thanks to implicits
// display the failing messages if there are any
(t: T) => (result(t).isSuccess,
results.filterNot(_._2.isSuccess).map { case (n, mr) =>
s"value $n is incorrect: ${mr.message}" }.mkString(", "))
}
You can test the code above. The failure messages are not the best but do the trick. In this situation:
httpClient.post("s1")
httpClient.post("s2")
there was two(httpClient).post(consecutiveValues(===("s1"), ===("s3")))
You will see:
[error] x test
[error] The mock was not called as expected:
[error] httpClient.post(
[error] value 1 is incorrect: 's2' is not equal to 's3'
[error] );
[error] Wanted 2 times:
[error] -> at ...
[error] But was 1 time:
[error] -> at ...