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?
Since Mockito any(Class) and anyInt family matchers perform a type check, thus they won't match null arguments. Instead use the isNull matcher.
Argument matchers are mainly used for performing flexible verification and stubbing in Mockito. It extends ArgumentMatchers class to access all the matcher functions. Mockito uses equal() as a legacy method for verification and matching of argument values.
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 ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With