ScalaMock
ScalaMock copied to clipboard
Issues mocking curried method with variable argument parameter
The documentation is unclear about how this interaction should be handled, and I can't seem to find a combination that works.
Given this trait:
trait DataStore {
def insertAt(timestamp: Long)(elements: String*): Unit
}
And this test suite:
package bugReport.scalamock
import org.scalatest.{ Matchers, WordSpec }
import org.scalamock.scalatest.MockFactory
class CurriedVarArgSpec extends WordSpec with Matchers with MockFactory {
"Mocking DataStore" should {
"be able to handled curried varargs" in {
val dataStore = mock[DataStore]
val timestamp = 40L
// Mock call to insertAt here
dataStore.insertAt(timestamp)("TestString")
}
}
}
I expected that mocking insertAt
would be fairly straightforward.
(dataStore.insertAt(_: Long)(_:String*)).expects(timestamp, "TestString")
However, this did not compile.
[error] .../bugReport/src/test/scala/scalamock/CurriedVarArgSpec.scala:14: ')' expected but identifier found.
[error] (dataStore.insertAt(_: Long)(_:String*)).expects(timestamp, "TestString")
[error] ^
[error] .../bugReport/src/test/scala/scalamock/CurriedVarArgSpec.scala:17: ')' expected but '}' found.
[error] }
[error] ^
[error] two errors found
Removing the offending *
didn't match the signature, but did compile.
(dataStore.insertAt(_: Long)(_:String)).expects(timestamp, "TestString")
Unfortunately, this caused the test to fail.
[info] - should be able to handled curried varargs *** FAILED ***
[info] Unexpected call: insertAt(40, WrappedArray(TestString))
[info]
[info] Expected:
[info] inAnyOrder {
[info] insertAt(40, TestString) once (never called - UNSATISFIED)
[info] }
[info]
[info] Actual:
[info] insertAt(40, WrappedArray(TestString)) (Option.scala:121)
Attempting to modify the mock to expect a WrappedArray was the next attempt.
import scala.collection.mutable.WrappedArray
(dataStore.insertAt(_: Long)(_: WrappedArray[String])).expects(timestamp, "TestString")
This took me back to a compilation error, which did confirm that String
was the appropriate type for that mock parameter.
[error] .../bugReport/src/test/scala/scalamock/CurriedVarArgSpec.scala:38: type mismatch;
[error] found : scala.collection.mutable.WrappedArray[String]
[error] required: String
[error] (dataStore.insertAt(_: Long)(_: WrappedArray[String])).expects(timestamp, "TestString")
[error] ^
[error] .../bugReport/src/test/scala/scalamock/CurriedVarArgSpec.scala:38: value expects is not a member of (Long, scala.collection.mutable.WrappedArray[String]) => Unit
[error] (dataStore.insertAt(_: Long)(_: WrappedArray[String])).expects(timestamp, "TestString")
[error] ^
[error] two errors found
One final stab it the dark was to keep the String
in the signature, but expect an Array
(WrappedArray
cannot be instantiated directly)
(dataStore.insertAt(_: Long)(_: String)).expects(timestamp, Array("TestString"))
This also does not compile.
[error] .../bugReport/src/test/scala/scalamock/CurriedVarArgSpec.scala:49: type mismatch;
[error] found : Array[String]
[error] required: org.scalamock.MockParameter[String]
[error] (dataStore.insertAt(_: Long)(_: String)).expects(timestamp, Array("TestString"))
[error] ^
[error] one error found
So, at this point, I'm stuck. Can this be mocked?
I'm seeing the same behaviour here. In my case the repeated parameter is in the first parameter list and the second parameter list is an implicit parameter list.
Example:
trait TypeClass[T]
object TypeClass {
implicit object StringTypeClass extends TypeClass[String]
}
class ClassUnderTest {
def mydef[T: TypeClass](a: String, b: String*): String = "result"
}
class TestBase extends FlatSpec with Matchers with MockFactory {
"repeated params" should "work" in {
val m = mock[ClassUnderTest]
(m.mydef(_: String, _: String)(_: TypeClass[String])).expects("a", "b", *).returning("mocked result")
tm.mydef("a", "b")
}
}
The above compiles but the test fails (correctly) because a Seq("b") is actually passed. Workaround (very ugly) by explicitly defining the parameter and exploiting type erasure:
val bParam = new MockParameter[Seq[String]](Seq("b"))
(t.mydef(_: String, _: String)(_: TypeClass[String])).expects("a", bParam.asInstanceOf[MockParameter[String]], *).returning("doof")
that is an interesting one. I experimented a bit myself:
test("varargs") {
trait Foo {
def foo(what: String*): String
def bar(i: Int)(what: String*): String
}
val m = mock[Foo]
m.foo _ expects Seq("a", "b" ) returning "bar" once()
(m.bar(_: Int)(_: (String*))) expects (42, "1") returning "baz" once()
m.foo("a", "b") === "bar"
m.bar(42)("1", "2") === "baz"
}
but that ends in a compiler error:
Error:(47, 25) no * parameter type allowed here
(m.bar (_: Int)(_: (String*))) expects (42, "1") returning "baz" once()
We are looking forward for the solution of this bug. Thank you so much.
If def bar(i: Int)(what: Any*): String
is mocked with (m.bar(_: Int)(_: Seq[_])) expects (42, Seq("1")) returning "baz" once()
everything works fine, but not with String*
as a type
I'm personally having trouble mocking a method as such
def someMethod(normalEasyToMockArgument: String, variableArgument: (String, Any)*): Unit
sorry, closed accidentally
Maybe this workaround is helpful in the meantime:
"Varargs in 2nd param list" should "be mockable" in {
trait TypeToMock {
def bar(i: Int)(what: String*): String
}
trait Workaround extends TypeToMock {
def bar(i: Int)(what: Any*): String
}
val m = mock[Workaround]
(m.bar(_: Int)(_: Seq[_])) expects (42, Seq("1", "2")) returning "baz" once()
m.bar(42)("1", "2") === "baz"
}
@barkhorn Any idea how to mock or create a workaround for this:
https://github.com/aerospike/aerospike-client-java/blob/master/client/src/com/aerospike/client/IAerospikeClient.java#L401-L453
I just have to mock the first get() method that doesn't have any varargs string parameter, but some overloaded methods of it do have which I am not using but the problem remains.
val aerospikeClient: IAerospikeClient = mock[IAerospikeClient]
(aerospikeClient.get: (Policy, Key) => Record)
.expects(new Policy(), new Key())
.returns(record)
.once()
With Mockito it works.
?
Shortened the interface a bit to not depend on any external stuff:
public interface IAerospikeClient {
public Record get(Policy policy, Key key) throws AerospikeException;
public Record get(Policy policy, Key key, String... binNames) throws AerospikeException;
}
Testcase:
import VarArgsWorkaround.Overloaded
import iaero.{IAerospikeClient, Key, Policy, Record}
import org.scalamock.function.MockFunction2
import org.scalamock.scalatest.MockFactory
import org.scalatest.{FunSuite, Matchers}
class VarArgsWorkaround extends FunSuite with MockFactory with Matchers {
test("workaround") {
val record = mock[Record]
val policy = mock[Policy]
val key = mock[Key]
val mockGet = mockFunction[Policy, Key, Record]
mockGet expects (policy, key) returns record once()
val mockClient = new Overloaded(mockGet)
mockClient.get(policy, key) should be (record)
}
}
object VarArgsWorkaround {
class Overloaded(mf: MockFunction2[Policy, Key, Record]) extends IAerospikeClient {
override def get(policy: Policy, key: Key): Record = mf(policy, key)
override def get(policy: Policy, key: Key, binNames: String*): Record = ???
}
}
probably need to create Record, Key, Policy differently. I stubbed them out to illustrate the pattern.
Note: Any further requests/discussions on workarounds needs to be StackOverflow please.
This thread seemed to go off the rails a bit. I'm a little confused. Is there still no way to use ScalaMock with String*
matching?
This works fine with 6.0.0-M1 and scala 3 since there is one parameter list and type is inferred.
trait DataStore {
def insertAt(timestamp: Long, elements: String*): Unit
}
val dataStore = mock[DataStore]
val timestamp = 40L
(dataStore.insertAt _).expects(*, Seq("TestString", "TestString2")).returns(())
dataStore.insertAt(timestamp, "TestString", "TestString2")
}
I'll search for solution with scala 3. At first glance it can be done. But not really simple