当单元测试在Android项目中生成grpc代码时,使用未实现的方法StatusRuntimeException命中

时间:2019-09-18 09:59:30

标签: android unit-testing kotlin mockito grpc

我有一个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'
            }
        }
    }
}

build.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
}

我目前无法找到到底会导致这种错误的原因。我也四处寻找答案/见解无济于事。

感谢任何输入。

2 个答案:

答案 0 :(得分:1)

sayHello没有实现,因此例外是正确的:

    private val serviceImpl = Mockito.mock(GreeterGrpc.GreeterImplBase::class.java, delegatesTo<Any>(object: GreeterGrpc.GreeterImplBase(){}))

该示例不会失败,因为HelloWorldClient捕获了异常并将其记录下来。如果您查看build/reports/tests/test/index.html并导航到HelloWorldClientTest,则标准错误包括:

Sep 18, 2019 9:18:11 AM io.grpc.examples.helloworld.HelloWorldClient greet
WARNING: RPC failed: Status{code=UNIMPLEMENTED, description=Method helloworld.Greeter/SayHello is unimplemented, cause=null}

要解决此问题,只需提供sayHello方法的虚假实现:

    private val serviceImpl = Mockito.mock(GreeterGrpc.GreeterImplBase::class.java, delegatesTo<Any>(object: GreeterGrpc.GreeterImplBase(){
        override fun sayHello(req: HelloRequest, responseObserver: StreamObserver<HelloReply>) {
            responseObserver.onNext(HelloReply.getDefaultInstance())
            responseObserver.onCompleted()
        }
    }))

我已提交issue 6161,以使示例更加清楚。

答案 1 :(得分:1)

您正在使用mockito-inline,它在模拟final方法。如果您交换到mockito-core,则测试将通过。我用example-kotlin进行了测试;将mockito依赖项交换为mockito-inline失败了测试。我会提到grpc-java强烈不支持覆盖最终方法。

作为快速解决方案,您可以使用:

Mockito.`when`(grpcService.bindService()).thenCallRealMethod()