如何避免在 scalamock 中重复模拟函数签名?
How to avoid duplication of mocked functions signatures in scalamock?
我正在使用 scalamock 来模拟这个 class:
class HttpService {
def post[In, Out]
(url: String, payload: In)
(implicit encoder: Encoder[In], decoder: Decoder[Out])
: Future[Out] = ...
...
}
...所以我的测试 class 有一个像这样使用的模拟:
val httpService = mock[HttpService]
(httpService.post[FormattedMessage, Unit](_ : String, _ : FormattedMessage) (_ : Encoder[FormattedMessage], _: Decoder[Unit]))
.expects("http://example.com/whatever",*, *, *)
.returning(Future.successful(()))
显然我必须编写整个模拟函数签名。如果我只把下划线放在签名中,而没有相应的类型,我会得到这样的错误:
[error] missing parameter type for expanded function ((x: <error>, x, x, x) => httpService.post[FormattedMessage, Unit](x, x)(x, x))
[error] (httpService.post[FormattedMessage, Unit](_, _) (_, _))
^
我不喜欢这段代码的地方是在测试中的几个地方使用了模拟期望,并且这个丑陋的签名在各处重复但具有不同的 In/Out 类型参数和期望。
所以我想我会写一个class
class HttpServiceMock extends MockFactory {
val instance = mock[HttpService]
def post[In, Out] = instance.post[In, Out](_ : String, _ : In) (_ : Encoder[In], _: Decoder[Out])
}
...并像这样使用它:
val httpService = new HttpServiceMock()
...
httpService.post[FormattedMessage, Unit]
.expects("http://example.com/whatever",*, *, *)
.returning(Future.successful(()))
...编译正常但是当我 运行 测试时我得到以下错误:
java.lang.NoSuchMethodException: com.myapp.test.tools.HttpServiceMock.mock$post[=15=]()
at java.lang.Class.getMethod(Class.java:1786)
at com.myapp.controllers.SlackControllerSpec.$anonfun$new(SlackControllerSpec.scala:160)
at org.scalatest.OutcomeOf.outcomeOf(OutcomeOf.scala:85)
at org.scalatest.OutcomeOf.outcomeOf$(OutcomeOf.scala:83)
at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
at org.scalatest.Transformer.apply(Transformer.scala:22)
at org.scalatest.Transformer.apply(Transformer.scala:20)
at org.scalatest.WordSpecLike$$anon.apply(WordSpecLike.scala:1078)
at org.scalatest.TestSuite.withFixture(TestSuite.scala:196)
at org.scalatest.TestSuite.withFixture$(TestSuite.scala:195)
我该如何解决这个错误?有没有其他方法可以避免一遍又一遍地重写模拟函数签名?
更新:
最后模拟看起来像这样:
trait HttpServiceMock extends MockFactory {
object httpService {
val instance = mock[HttpService]
def post[In, Out] = toMockFunction4(instance.post[In, Out](_: String, _: In)(_: Encoder[In], _: Decoder[Out]))
}
}
不要使用 Scalamock,使 HttpService
成为特征并直接实现特征以模拟您需要的任何东西。例如。 (您可以将其粘贴到 Scala REPL 中,但请记住按 Enter 和 Ctrl+D结束):
:rese
:pa
import scala.concurrent.Future
trait Encoder[A]
trait Decoder[A]
// HttpService.scala
trait HttpService {
def post[In: Encoder, Out: Decoder](
url: String, payload: In): Future[Out]
}
object HttpService extends HttpService {
override def post[In: Encoder, Out: Decoder](
url: String,
payload: In):
Future[Out] = ???
}
// HttpServiceSpec.scala
class Mock[Out](result: Future[Out]) extends HttpService {
override def post[In: Encoder, Out: Decoder](
url: String,
payload: In):
Future[Out] =
// This is fine because it's a mock.
result.asInstanceOf[Future[Out]]
}
您可以使用以下代码:
trait HttpMockSupport {
this: MockFactory =>
val httpService = mock[HttpService]
def prettyPost[In, Out]: MockFunction4[String, In, Encoder[In], Decoder[Out], Future[Out]] = {
toMockFunction4(httpService.post[In, Out](_: String, _: In)(_: Encoder[In], _: Decoder[Out]))
}
}
class AClassThatNeedsHttpServiceMocking extends FreeSpec with Matchers with MockFactory with HttpMockSupport {
"HttpService should post" in {
val url = "http://localhost/1"
val input = "input"
implicit val encoder: Encoder[String] = new Encoder[String] {}
implicit val decoder: Decoder[String] = new Decoder[String] {}
prettyPost[String, String]
.expects(url, input, encoder, decoder)
.returns(Future.successful("result"))
httpService.post(url, input)
}
}
它将通用模拟放在一个特征中,可以在所有需要模拟 HttpService 的地方扩展,只需调用 non-ugly 方法:)
更新 1:
更新它以接受预期的参数。
更新二:
将 prettyPost 方法更新为通用的,以便我们可以设置任何类型的期望。
Scalamock 需要一个 MockFunctionX。因此,在您的情况下,您所要做的就是将丑陋的函数转换为漂亮的函数,然后将其转换为 MockFunctionX。
我正在使用 scalamock 来模拟这个 class:
class HttpService {
def post[In, Out]
(url: String, payload: In)
(implicit encoder: Encoder[In], decoder: Decoder[Out])
: Future[Out] = ...
...
}
...所以我的测试 class 有一个像这样使用的模拟:
val httpService = mock[HttpService]
(httpService.post[FormattedMessage, Unit](_ : String, _ : FormattedMessage) (_ : Encoder[FormattedMessage], _: Decoder[Unit]))
.expects("http://example.com/whatever",*, *, *)
.returning(Future.successful(()))
显然我必须编写整个模拟函数签名。如果我只把下划线放在签名中,而没有相应的类型,我会得到这样的错误:
[error] missing parameter type for expanded function ((x: <error>, x, x, x) => httpService.post[FormattedMessage, Unit](x, x)(x, x))
[error] (httpService.post[FormattedMessage, Unit](_, _) (_, _))
^
我不喜欢这段代码的地方是在测试中的几个地方使用了模拟期望,并且这个丑陋的签名在各处重复但具有不同的 In/Out 类型参数和期望。
所以我想我会写一个class
class HttpServiceMock extends MockFactory {
val instance = mock[HttpService]
def post[In, Out] = instance.post[In, Out](_ : String, _ : In) (_ : Encoder[In], _: Decoder[Out])
}
...并像这样使用它:
val httpService = new HttpServiceMock()
...
httpService.post[FormattedMessage, Unit]
.expects("http://example.com/whatever",*, *, *)
.returning(Future.successful(()))
...编译正常但是当我 运行 测试时我得到以下错误:
java.lang.NoSuchMethodException: com.myapp.test.tools.HttpServiceMock.mock$post[=15=]()
at java.lang.Class.getMethod(Class.java:1786)
at com.myapp.controllers.SlackControllerSpec.$anonfun$new(SlackControllerSpec.scala:160)
at org.scalatest.OutcomeOf.outcomeOf(OutcomeOf.scala:85)
at org.scalatest.OutcomeOf.outcomeOf$(OutcomeOf.scala:83)
at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
at org.scalatest.Transformer.apply(Transformer.scala:22)
at org.scalatest.Transformer.apply(Transformer.scala:20)
at org.scalatest.WordSpecLike$$anon.apply(WordSpecLike.scala:1078)
at org.scalatest.TestSuite.withFixture(TestSuite.scala:196)
at org.scalatest.TestSuite.withFixture$(TestSuite.scala:195)
我该如何解决这个错误?有没有其他方法可以避免一遍又一遍地重写模拟函数签名?
更新: 最后模拟看起来像这样:
trait HttpServiceMock extends MockFactory {
object httpService {
val instance = mock[HttpService]
def post[In, Out] = toMockFunction4(instance.post[In, Out](_: String, _: In)(_: Encoder[In], _: Decoder[Out]))
}
}
不要使用 Scalamock,使 HttpService
成为特征并直接实现特征以模拟您需要的任何东西。例如。 (您可以将其粘贴到 Scala REPL 中,但请记住按 Enter 和 Ctrl+D结束):
:rese
:pa
import scala.concurrent.Future
trait Encoder[A]
trait Decoder[A]
// HttpService.scala
trait HttpService {
def post[In: Encoder, Out: Decoder](
url: String, payload: In): Future[Out]
}
object HttpService extends HttpService {
override def post[In: Encoder, Out: Decoder](
url: String,
payload: In):
Future[Out] = ???
}
// HttpServiceSpec.scala
class Mock[Out](result: Future[Out]) extends HttpService {
override def post[In: Encoder, Out: Decoder](
url: String,
payload: In):
Future[Out] =
// This is fine because it's a mock.
result.asInstanceOf[Future[Out]]
}
您可以使用以下代码:
trait HttpMockSupport {
this: MockFactory =>
val httpService = mock[HttpService]
def prettyPost[In, Out]: MockFunction4[String, In, Encoder[In], Decoder[Out], Future[Out]] = {
toMockFunction4(httpService.post[In, Out](_: String, _: In)(_: Encoder[In], _: Decoder[Out]))
}
}
class AClassThatNeedsHttpServiceMocking extends FreeSpec with Matchers with MockFactory with HttpMockSupport {
"HttpService should post" in {
val url = "http://localhost/1"
val input = "input"
implicit val encoder: Encoder[String] = new Encoder[String] {}
implicit val decoder: Decoder[String] = new Decoder[String] {}
prettyPost[String, String]
.expects(url, input, encoder, decoder)
.returns(Future.successful("result"))
httpService.post(url, input)
}
}
它将通用模拟放在一个特征中,可以在所有需要模拟 HttpService 的地方扩展,只需调用 non-ugly 方法:)
更新 1:
更新它以接受预期的参数。
更新二:
将 prettyPost 方法更新为通用的,以便我们可以设置任何类型的期望。
Scalamock 需要一个 MockFunctionX。因此,在您的情况下,您所要做的就是将丑陋的函数转换为漂亮的函数,然后将其转换为 MockFunctionX。