android - 在 Android 项目中对生成的 grpc 代码进行单元测试时,使用未实现的方法 StatusRuntimeException 命中
问题描述
我有一个 Android 项目,它在构建时从 proto 文件生成 gRPC 类,它来自 helloworld 示例下的此处: https ://github.com/grpc/grpc-java/tree/master/examples/src
我的 JUnit 测试遵循为 HelloWorldClient 编写的测试(https://github.com/grpc/grpc-java/blob/master/examples/example-kotlin/src/test/kotlin/io/grpc/examples/helloworld /HelloWorldClientTest.kt),我认为理论上可行
但是,当我运行测试时,我遇到了一个异常错误,如下所示:
io.grpc.StatusRuntimeException: UNIMPLEMENTED: Method helloworld.Greeter/SayHello is unimplemented
at io.grpc.stub.ClientCalls.toStatusRuntimeException(ClientCalls.java:235)
at io.grpc.stub.ClientCalls.getUnchecked(ClientCalls.java:216)
at io.grpc.stub.ClientCalls.blockingUnaryCall(ClientCalls.java:141)
at io.grpc.examples.helloworld.GreeterGrpc$GreeterBlockingStub.sayHello(GreeterGrpc.java:177)
at com.android.grpcmvvm.data.GreeterRemoteDataSource.sayHello(GreeterRemoteDataSource.kt:49)
at com.android.grpcmvvm.view.GreeterViewModelUnitTest.testTestFunction(GreeterViewModelUnitTest.kt:109)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at io.grpc.testing.GrpcCleanupRule$1.evaluate(GrpcCleanupRule.java:125)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Wanted but not invoked:
serviceImpl.sayHello(
<Capturing argument>,
<any>
);
-> at io.grpc.examples.helloworld.GreeterGrpc$GreeterImplBase.sayHello(GreeterGrpc.java:101)
However, there was exactly 1 interaction with this mock:
serviceImpl.bindService();
-> at io.grpc.internal.AbstractServerImplBuilder.addService(AbstractServerImplBuilder.java:114)
Wanted but not invoked:
serviceImpl.sayHello(
<Capturing argument>,
<any>
);
-> at io.grpc.examples.helloworld.GreeterGrpc$GreeterImplBase.sayHello(GreeterGrpc.java:101)
However, there was exactly 1 interaction with this mock:
serviceImpl.bindService();
-> at io.grpc.internal.AbstractServerImplBuilder.addService(AbstractServerImplBuilder.java:114)
at io.grpc.examples.helloworld.GreeterGrpc$GreeterImplBase.sayHello(GreeterGrpc.java:101)
at com.android.grpcmvvm.view.GreeterViewModelUnitTest.testTestFunction(GreeterViewModelUnitTest.kt:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at io.grpc.testing.GrpcCleanupRule$1.evaluate(GrpcCleanupRule.java:125)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
以下是测试和相关代码(请注意 ViewModel 名称。出于此问题的目的,我从测试中删除了有关 ViewModel 的任何内容)
GreeterViewModelUnitTest.kt
import com.android.grpcmvvm.data.GreeterRemoteDataSource
import com.android.grpcmvvm.grpc.GrpcService
import io.grpc.ManagedChannel
import io.grpc.examples.helloworld.GreeterGrpc
import io.grpc.examples.helloworld.HelloReply
import io.grpc.examples.helloworld.HelloRequest
import io.grpc.inprocess.InProcessChannelBuilder
import io.grpc.inprocess.InProcessServerBuilder
import io.grpc.stub.StreamObserver
import io.grpc.testing.GrpcCleanupRule
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.*
import org.mockito.AdditionalAnswers.delegatesTo
import org.mockito.internal.matchers.Any
@RunWith(JUnit4::class)
class GreeterViewModelUnitTest {
// region JUnit test rules
@get:Rule
val grpcCleanupRule = GrpcCleanupRule()
private val serviceImpl = Mockito.mock(GreeterGrpc.GreeterImplBase::class.java, delegatesTo<Any>(object: GreeterGrpc.GreeterImplBase(){}))
// endregion
// region Private properties
@Mock
private lateinit var grpcService: GrpcService
private lateinit var managedChannel: ManagedChannel
private lateinit var greeterRemoteDataSource: GreeterRemoteDataSource
// endregion
@Before
@Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
val serverName = InProcessServerBuilder.generateName()
grpcCleanupRule.register(InProcessServerBuilder
.forName(serverName)
.directExecutor()
.addService(serviceImpl)
.build()
.start())
managedChannel = grpcCleanupRule.register(InProcessChannelBuilder
.forName(serverName)
.directExecutor()
.build())
Mockito.`when`(grpcService.createManagedChannel()).thenReturn(managedChannel)
greeterRemoteDataSource = GreeterRemoteDataSource(grpcService)
}
@Test
fun testTestFunction() {
val requestCaptor = ArgumentCaptor.forClass(HelloRequest::class.java)
greeterRemoteDataSource.sayHello("once again")
Mockito.verify<GreeterGrpc.GreeterImplBase>(serviceImpl)
.sayHello(requestCaptor.capture(), ArgumentMatchers.any<StreamObserver<HelloReply>>())
assertEquals("once again", requestCaptor.value.name)
}
GrpcService.kt
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import java.util.concurrent.Executors
class GrpcService(private val host: String, private val port: Int) {
fun createManagedChannel(): ManagedChannel {
return ManagedChannelBuilder
.forAddress(host, port)
.executor(Executors.newSingleThreadExecutor())
.usePlaintext()
.build()
}
}
GreeterRemoteDataSource.kt
import com.android.grpcmvvm.grpc.GrpcService
import io.grpc.ManagedChannel
import io.grpc.StatusRuntimeException
import io.grpc.examples.helloworld.GreeterGrpc
import io.grpc.examples.helloworld.HelloReply
import io.grpc.examples.helloworld.HelloRequest
class GreeterRemoteDataSource constructor(private val grpcService: GrpcService) {
private lateinit var channel: ManagedChannel
fun sayHello(message: String): String {
channel = grpcService.createManagedChannel()
val stub = GreeterGrpc.newBlockingStub(channel)
val request = HelloRequest.newBuilder().setName(message).build()
val reply: HelloReply = try {
stub.sayHello(request)
} catch (e: StatusRuntimeException) {
e.printStackTrace()
return String.format("{0}", e.status)
} finally {
channel.shutdown()
}
return reply.message
}
}
build.gradle(应用程序)
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.android.grpcmvvm"
minSdkVersion 15
targetSdkVersion 28
multiDexEnabled true
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
def grpc_version = "1.22.1"
def lifecycle_version = "2.2.0-alpha04"
def mockito_kotlin_version = "2.1.0"
def mockito_version = "3.0.0"
def multidex_version = "2.0.1"
def truth_version = "0.45"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "androidx.multidex:multidex:$multidex_version"
implementation 'com.google.android.material:material:1.0.0'
// gRPC
implementation 'javax.annotation:javax.annotation-api:1.3.2'
implementation "io.grpc:grpc-okhttp:$grpc_version"
implementation "io.grpc:grpc-protobuf-lite:$grpc_version"
implementation "io.grpc:grpc-stub:$grpc_version"
// Lifecycle
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// Instrumented tests
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
// Unit tests
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "com.google.truth:truth:$truth_version"
testImplementation "io.grpc:grpc-testing:$grpc_version"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$mockito_kotlin_version"
testImplementation 'junit:junit:4.12'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.1'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.1'
testImplementation "org.mockito:mockito-inline:$mockito_version"
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.9.0'
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:1.22.1"
}
javalite {
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
}
}
generateProtoTasks {
all()*.plugins {
javalite {}
}
ofNonTest()*.plugins {
grpc {
// Options added to --grpc_out
option 'lite'
}
}
}
}
构建.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
我目前无法找到究竟是什么会导致这样的错误。我也四处寻找答案/见解无济于事。
任何输入表示赞赏。
解决方案
您正在使用mockito-inline
,它正在模拟一种final
方法。如果你换成mockito-core
然后测试应该通过。我用 example-kotlin 对此进行了测试;将 mockito 依赖项交换为 mockito-inline 破坏了测试。我会提到 grpc-java 强烈不支持覆盖 final 方法。
作为快速修复,您可以使用:
Mockito.`when`(grpcService.bindService()).thenCallRealMethod()
推荐阅读
- javafx - 在可观察列表 (JavaFX 8) 内的大量文件(对象)上更快地迭代
- java - 如何从西方日期格式转换为日本日期格式?
- odata - Microsoft Graph API - 搜索主题中包含 # 的电子邮件时出现未终止的字符串文字错误
- java - 如何使用 Java 从 XSD 生成 XML 数据?
- jquery - 如何使用 jquery 添加滚动分页?
- java - 结构如何正确通知其他线程有关事件?
- templates - aurelia 自定义元素/属性的插件开发模板
- android - .firebase.database.DatabaseReference com.google.firebase.database.FirebaseDatabase.getReference(java.lang.String)' 在空对象引用上
- google-api - 不需要验证的应用会显示“此应用未验证”警告
- vb.net - 此代码在 MS Access 中更新数据库值是否有任何问题?