我正在尝试使用Mockito模拟一个通用方法。我正在尝试模拟的方法在泛型类型ReaderT
上创建T
,并期望隐式转换也可用于将Output
类型转换为泛型类型{{ 1}}提供。
实施并不重要,但这里减少了方法本身:
T
该方法在运行API时可以正常工作,但是,在模拟时我收到错误,然后在我的测试中执行它。我已经完成了模拟的几次迭代,这就是我现在所拥有的:
/**
* The return type of WebReaderT is held within the class scope.
* It pre-populates some of the types for ReaderT.
*/
def createToken[T](authRequest: Input, tokenTtl: TokenTtlConfig)(implicit f: Output => T): WebReaderT[T]
因为我无法在模拟本身中使用通配符类型,例如when(mock.createToken[Any](any[AuthAdapter.Input], any[TokenTtlConfig])(any[AuthAdapter.Output => Any])) thenAnswer { invocation =>
val tokenTtl = invocation.getArgument[TokenTtlConfig](1)
tokenTtl match {
case config.tokenTtlMap.v0Tokens => mockCreateToken[LoginResponse](tokenTtl)
case config.tokenTtlMap.v1Tokens => mockCreateToken[AccessTokenResponse](tokenTtl)
}
}
/**
* This method is functionally a direct copy of the method that
* it's effectively mocking.
*/
def mockCreateToken[T](tokenTtl: TokenTtlConfig)(implicit f: AuthAdapter.Output => T): WebReaderT[T] = {
ReaderT.lift[EitherTError, SentinelEnv[Future], T](EitherT.fromEither[Future](Right(AuthAdapter.Output(
mockUser1._id,
mockUser1._id,
tokenTtl.accessTtl.map(AccessToken(DateTime.now, _, "foo")),
tokenTtl.refreshTtl.map(RefreshToken(DateTime.now, _, "bar"))
))))
}
(据我所知),我必须与T
匹配,然后确定预期的输出类型将Any
与一组已知值匹配。这显然是我确定输出类型的一种非常可疑的方式,因为它在很大程度上依赖于应用程序当前在内部使用tokenTtl
值。
为了避免这种糟糕的方法,我希望Mockito能够匹配传递给这样的泛型方法的类型:
config.tokenTtlMap.<?>
这个模拟实现显然是最好的方法,但据我所知,Mockito只是忽略了第一个模拟(第二个重写它吗?),因此没有正确匹配传递给它的预期输出类型通用方法。
尽管我付出了最大的努力,但这两种实现方式都会导致// Only match "LoginResponse"
when(mockAuthAdapter.createToken[LoginResponse](any[AuthAdapter.Input], any[TokenTtlConfig])(any[AuthAdapter.Output => LoginResponse])) thenAnswer { invocation =>
mockCreateToken[LoginResponse](invocation.getArgument[TokenTtlConfig](1))
}
// Only match "AccessTokenResponse"
when(mockAuthAdapter.createToken[AccessTokenResponse](any[AuthAdapter.Input], any[TokenTtlConfig])(any[AuthAdapter.Output => AccessTokenResponse])) thenAnswer { invocation =>
mockCreateToken[AccessTokenResponse](invocation.getArgument[TokenTtlConfig](1))
}
这样:
NullPointerException
可以在此PasteBin
找到完整错误
我非常感谢你解决这个问题的任何帮助!
答案 0 :(得分:1)
你正在用一个名字命名的墙:“类型擦除”。当没有关于泛型的信息被维护时,Mockito纯粹在运行时运行。在使用ClassTag
或TypeTag
的大多数情况下,有很多方法可以在scala中处理此问题,但我怀疑在mockito的情况下可以很容易地解决它。
有以下问题:
Mockito在运行时使用方法调用堆栈注册mock,因此任何类型擦除的解决方法都需要直接传递这些aditional implicits,请考虑以下内容:
def myMethod[A](argument: Input): Output
//in tests
when(mock.myMethod[Int](any[Input])) thanAnswer {???}
when(mock.myMethod[Int](any[Input])) thanAnswer {???}
第二个定义首先覆盖,因为您已经正确观察到了。因此,我们可以尝试通过使用ClassTags解决此问题:
def myMethod[A:ClassTag](argument: Input): Output
//in tests
when(mock.myMethod[Int](any[Input])) thanAnswer {???}
when(mock.myMethod[Double](any[Input])) thanAnswer {???}
现在隐式类标记参数怎么样?首先,我不确定mockito将如何与它互动。其次,您只使用不必要的隐式参数来污染您的方法签名,因为需要编写测试。
您可以尝试使用某些特定于scala的解决方案,例如scala mock。