Fadelis / grpcmock

A gRPC Java testing tool to easily mock endpoints of gRPC services for IT or Unit testing
http://grpcmock.org
Apache License 2.0
144 stars 13 forks source link

`UNIMPLEMENTED` with Kotest and Kotlin gRPC services #30

Closed savbiz closed 1 year ago

savbiz commented 1 year ago

Does the library support Kotest and Grpc services implemented in Kotlin?

I keep getting UNIMPLEMENTED even though I can see in the logs that the requests hit GrpcMock. I suspect that the stubFor doesn't really work properly with those 2 Kotlin libraries.

import io.grpc.ManagedChannel
import io.grpc.inprocess.InProcessChannelBuilder
import io.kotest.core.spec.Spec
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.result.shouldBeSuccess
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import org.assertj.core.api.Assertions.assertThat
import org.grpcmock.GrpcMock
import org.grpcmock.GrpcMock.calledMethod
import org.grpcmock.GrpcMock.stubFor
import org.grpcmock.GrpcMock.unaryMethod
import org.grpcmock.GrpcMock.verifyThat
import org.grpcmock.springboot.AutoConfigureGrpcMock
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles

@SpringBootTest
@MockDatabase
@AutoConfigureGrpcMock(useInProcessServer = true, name = "test")
@ActiveProfiles("test")
class XGrpcClientTestLib(
    val client: XGrpcClient,
    val grpcMock: GrpcMock,
) : FunSpec() {

    @Value("\${grpcmock.server.name}")
    private val inProcessName: String? = null

    lateinit var channel: ManagedChannel

    override suspend fun beforeSpec(spec: Spec) {
        channel = InProcessChannelBuilder.forName(inProcessName)
            .usePlaintext()
            .build();
    }

    override suspend fun afterSpec(spec: Spec) {
        channel.shutdownNow()
    }

    init {
        test("getX should return correct response") {
            val expectedRequest = GetXRequest.newBuilder()
                .setXName("name")
                .setXKey("x)
                .build()
            val expectedResponse = GetXResponse.newBuilder()
                .setX("success")
                .build()

            stubFor(
                unaryMethod(XGrpcServiceGrpcKt.getXForUserMethod)
                    .withRequest(expectedRequest)
                    .willReturn(expectedResponse)
            )

            val variant = client.getVariant(xName, xKey)

            variant.shouldBeSuccess().shouldBe("xSuccess")

            verifyThat(
                calledMethod(XGrpcServiceGrpcKt.getXForUserMethod)
                    .withRequest(expectedRequest),
            )
        }
    }
}
Fadelis commented 1 year ago

Hi @savbiz, I haven't used these libraries at any capacity, so I can't tell for sure. But from what you've described it seems that your grpcmock instance is started on one thread and stubs are configured on a different one and in order to use the static methods it has to be on the same one as it depends on ThreadLocals. You can try to register the stub methods directly on the grpcmock instance that you're injecting in constructor instead.

savbiz commented 1 year ago

Thanks for the pointer, @Fadelis!

Registering the stub methods directly on my instance did the trick. :)

Example here:

            val expectedRequest = GetXRequest.newBuilder()
                .setXName("name")
                .setXKey("x)
                .build()

            val expectedResponse = GetXResponse.newBuilder()
                .setX("success")
                .build()

            grpcMock.register(
                unaryMethod(XGrpcServiceGrpcKt.getXForUserMethod)
                    .withRequest(expectedRequest)
                    .willReturn(expectedResponse),
            )

            val requestPattern =
                RequestPatternBuilderImpl(XGrpcServiceGrpcKt.getXForUserMethod)
                    .withStatusOk()
                    .withRequest(expectedRequest)
                    .withHeader("X", "value)
                    .build()

            grpcMock.verifyThat(
                requestPattern,
                once(),
            )