You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何Mock Kotlin SupabaseClient进行单元测试?

问题解决方案

一、单元测试修复

你遇到的错误是因为supabaseClient.postgrest是扩展属性,底层依赖supabaseClient.plugin(Postgrest)获取插件实例,直接Mock普通属性的方式会触发真实的扩展逻辑,导致检查插件是否存在的断言失败。

正确的Mockk配置步骤

  1. Mock所有需要的Supabase相关实例
  2. 替换插件获取逻辑(替代扩展属性的调用)
  3. Mock表访问和insert方法,捕获传入的参数用于断言

示例代码:

import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.postgrest.Postgrest
import io.github.jan.supabase.postgrest.PostgrestBuilder
import io.github.jan.supabase.postgrest.PostgrestResult
import io.mockk.*
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class GitHubTrafficStoreTest {
    private val supabaseClient = mockk<SupabaseClient>()
    private val postgrest = mockk<Postgrest>()
    private val postgrestBuilder = mockk<PostgrestBuilder>()
    private val postgrestResult = mockk<PostgrestResult>()
    private val insertValuesSlot = slot<List<Clone>>() // 替换为你实际的Clone类型

    @BeforeEach
    fun setUp() {
        // 1. Mock插件获取,替代postgrest扩展属性的逻辑
        every { supabaseClient.plugin(Postgrest) } returns postgrest

        // 2. Mock表访问:访问"clone"表返回对应的Builder
        every { postgrest["clone"] } returns postgrestBuilder

        // 3. Mock insert调用,捕获传入的values参数
        coEvery {
            postgrestBuilder.insert(
                values = capture(insertValuesSlot),
                upsert = true,
                onConflict = "owner, repository, timestamp"
            )
        } returns postgrestResult

        // 4. Mock decodeList返回预期结果
        coEvery { postgrestResult.decodeList<ResultClone>() } returns emptyList()
    }

    @Test
    fun `saveClones converts and inserts correct data`() = runTest {
        // 准备测试数据
        val testOwner = "test-owner"
        val testRepo = "test-repo"
        val testClones = GitHubClones(
            clones = listOf(GitHubClone(count = 5, uniques = 3, timestamp = "2024-01-01T00:00:00Z"))
        )

        // 调用测试方法
        val result = yourStoreInstance.saveClones(testOwner, testRepo, testClones)

        // 断言传入insert的数据符合预期
        val insertedData = insertValuesSlot.captured
        assert(insertedData.size == 1)
        with(insertedData.first()) {
            assert(owner == testOwner)
            assert(repository == testRepo)
            assert(count == 5)
            assert(uniques == 3)
            assert(timestamp == "2024-01-01T00:00:00Z")
        }

        // 断言返回结果符合预期
        assert(result.isEmpty())
    }
}

二、集成测试方案

Supabase可以用Testcontainers进行集成测试,核心是利用Postgres的Testcontainer(因为Supabase本质是增强版Postgres),步骤如下:

1. 添加Testcontainers依赖

在你的构建文件中添加Postgres Testcontainer依赖:

// build.gradle.kts
testImplementation("org.testcontainers:postgresql:1.19.8")
testImplementation("org.testcontainers:junit-jupiter:1.19.8")

2. 编写集成测试

启动Postgres容器,初始化Supabase所需的表结构,创建真实的SupabaseClient连接,验证数据持久化逻辑:

import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.postgrest.Postgrest
import kotlinx.coroutines.runTest
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.testcontainers.containers.PostgresContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

@Testcontainers
class GitHubTrafficStoreIntegrationTest {
    companion object {
        @Container
        val postgresContainer = PostgresContainer("postgres:15-alpine")
            .withDatabaseName("supabase_test")
            .withUsername("postgres")
            .withPassword("postgres")

        private lateinit var supabaseClient: SupabaseClient

        @BeforeAll
        @JvmStatic
        fun setUp() {
            // 创建连接到Testcontainer的SupabaseClient
            supabaseClient = createSupabaseClient(
                supabaseUrl = postgresContainer.jdbcUrl.replace("jdbc:postgresql:", "http://"),
                supabaseKey = "test-key"
            ) {
                install(Postgrest)
            }

            // 初始化clone表结构
            runTest {
                supabaseClient.postgrest["clone"].execute(
                    """
                    CREATE TABLE clone (
                        owner TEXT,
                        repository TEXT,
                        count INTEGER,
                        uniques INTEGER,
                        timestamp TEXT,
                        PRIMARY KEY (owner, repository, timestamp)
                    )
                    """.trimIndent()
                )
            }
        }
    }

    @Test
    fun `saveClones persists data correctly`() = runTest {
        val store = YourGitHubTrafficStore(supabaseClient)
        val testOwner = "test-owner"
        val testRepo = "test-repo"
        val testClones = GitHubClones(
            clones = listOf(GitHubClone(count = 5, uniques = 3, timestamp = "2024-01-01T00:00:00Z"))
        )

        // 调用保存方法
        store.saveClones(testOwner, testRepo, testClones)

        // 查询数据库验证数据
        val savedData = supabaseClient.postgrest["clone"]
            .select()
            .eq("owner", testOwner)
            .eq("repository", testRepo)
            .decodeList<Clone>()

        assert(savedData.size == 1)
        with(savedData.first()) {
            assert(count == 5)
            assert(uniques == 3)
            assert(timestamp == "2024-01-01T00:00:00Z")
        }
    }
}

内容的提问来源于stack exchange,提问作者jlengrand

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 02:02:09