'Mock Bigquery function in kotlin unit test

I am develop an unit test, which involved (com.google.api.services.bigquery.Bigquery). I mock the object with mockk @SpyK. But the every{} block report error when unit test start. Detail as follow

the dependency is:

"com.google.apis:google-api-services-bigquery:v2-rev20220326-1.32.1"

The exception is throw in setUp() function

Code

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(MockKExtension::class, SpringExtension::class)
@EnableConfigurationProperties(ConfidentialTags::class)
@PropertySource(value = ["classpath:application.yml"], factory = YamlPropertySourceFactory::class)
class ResourceAccessServiceTest {
    private val mockGoogleCredential = MockGoogleCredential.Builder().build()

    @SpyK
    private var olderBigQuery: Bigquery = Bigquery.Builder(
        MockGoogleCredential.newMockHttpTransportWithSampleTokenResponse(),
        mockGoogleCredential.jsonFactory,
        mockGoogleCredential
    ).build()

    @InjectMockKs
    private lateinit var resourceAccessService: ResourceAccessService

 @BeforeAll
    fun setUp() {
        //error reported here
        every { olderBigQuery.RowAccessPolicies().list(any(), any(), any()).execute() } returns mockk() {
            every { rowAccessPolicies } returns listOf()
        }
    }



    @Test
    fun queryRowAccessExistedPrincipals() {
        val list = resourceAccessService.queryRowAccessExistedPrincipals(
            TableId.of("proj", "ds", "tbl"),
            RowFilter("region", listOf("hk"))
        )
        assert(list.isEmpty())
    }
}

error log

java.net.MalformedURLException: no protocol: projects/2af774bb308513d5/datasets/-3e5359fe6b1a603f/tables/-28b905709f94ecc7/rowAccessPolicies
java.lang.IllegalArgumentException: java.net.MalformedURLException: no protocol: projects/2af774bb308513d5/datasets/-3e5359fe6b1a603f/tables/-28b905709f94ecc7/rowAccessPolicies
    at com.google.api.client.http.GenericUrl.parseURL(GenericUrl.java:679)
    at com.google.api.client.http.GenericUrl.<init>(GenericUrl.java:125)
    at com.google.api.client.http.GenericUrl.<init>(GenericUrl.java:108)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.buildHttpRequestUrl(AbstractGoogleClientRequest.java:373)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.buildHttpRequest(AbstractGoogleClientRequest.java:404)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:514)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:455)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:565)
    at com.airwallex.data.grpc.das.service.ResourceAccessServiceTest$setUp$1.invoke(ResourceAccessServiceTest.kt:67)
    at com.airwallex.data.grpc.das.service.ResourceAccessServiceTest$setUp$1.invoke(ResourceAccessServiceTest.kt:67)
    at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$1.invoke(RecordedBlockEvaluator.kt:25)
    at io.mockk.impl.eval.RecordedBlockEvaluator$enhanceWithRethrow$1.invoke(RecordedBlockEvaluator.kt:78)
    at io.mockk.impl.recording.JvmAutoHinter.autoHint(JvmAutoHinter.kt:23)
    at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:40)
    at io.mockk.impl.eval.EveryBlockEvaluator.every(EveryBlockEvaluator.kt:30)
    at io.mockk.MockKDsl.internalEvery(API.kt:93)
    at io.mockk.MockKKt.every(MockK.kt:98)
    at com.airwallex.data.grpc.das.service.ResourceAccessServiceTest.setUp(ResourceAccessServiceTest.kt:67)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptLifecycleMethod(TimeoutExtension.java:126)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptBeforeAllMethod(TimeoutExtension.java:68)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeBeforeAllMethods$9(ClassBasedTestDescriptor.java:384)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeBeforeAllMethods(ClassBasedTestDescriptor.java:382)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:196)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:78)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:136)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)

The key error info is

java.net.MalformedURLException: no protocol: projects/2af774bb308513d5/datasets/-3e5359fe6b1a603f/tables/-28b905709f94ecc7/rowAccessPolicies

The exception is thrown in the every{} block in setUp function. It seems the url built inside the SDK is invalid, without protocol header. But I don't know how to solve the problems



Solution 1:[1]

So what you really want is a way to decouple the code under test from BigQuery. The better way to do so that is to hide BigQuery behind your own abstraction (e.g. an interface) and make the code that needs to query only depend on that abstraction - look up the dependency inversion principle/dependency injection (I've got some answers here that show examples). That way, you can substitute a fake implementation in for testing that, say, queries against a collection in memory.

There is still one problem with this approach. You do at some point need to test against BigQuery for real. Otherwise, how will you prove that component works? I guess the possibilities here are constrained by what your company allows.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ndc85430