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

Android单元测试中Mock AuthToken方法失败问题排查

问题:Mock AuthToken方法时触发错误,私有方法仍执行的原因及解决办法

代码与错误场景

AuthToken数据类代码

@JsonClass(generateAdapter = true)
data class AuthToken(
    @Json(name = "token") val access: String,
    @Json(name = "refreshToken") val refresh: String
) {
    fun isAccessTokenValid(): Boolean = isTokenValid(access)

    fun isRefreshTokenValid(): Boolean = isTokenValid(refresh)

    private fun isTokenValid(token: String): Boolean {
        return try {
            val jwt = JWT(token)
            val isExpired = jwt.isExpired(0)
            Timber.d("Token ${if (isExpired) "expired" else "valid"} [${jwt.details()}]")
            !isExpired
        } catch (e: DecodeException) {
            Timber.w(e, "Invalid auth token.")
            false
        }
    }
}

单元测试代码

val authToken = AuthToken("accessToken", "refreshToken")
every { authToken.isAccessTokenValid() } returns true
every { authToken.isRefreshTokenValid() } returns false

触发的错误

Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock.

错误发生在val jwt = JWT(token)行。


原因分析

  1. 操作的是真实对象而非Mock对象:直接用AuthToken()构造的是真实实例,Mock框架(比如MockK)无法拦截真实对象的方法调用,调用isAccessTokenValid()时会直接执行真实逻辑,包括内部调用的私有方法isTokenValid,自然会走到JWT(token)这行代码。
  2. every { ... }只对Mock实例生效:这个语法是用来定义Mock对象的方法返回值,真实对象调用时框架识别不到Mock配置,所以报错提示缺少Mock调用。

解决方法

方法1:直接Mock AuthToken对象

用MockK的mockk<AuthToken>()创建Mock实例,替代真实构造:

val authToken = mockk<AuthToken>()
every { authToken.isAccessTokenValid() } returns true
every { authToken.isRefreshTokenValid() } returns false

这样框架会完全拦截方法调用,不会执行任何真实逻辑,包括内部的私有方法。

方法2:重构代码,依赖注入JWT校验逻辑

如果需要测试AuthToken本身的逻辑(而非单纯Mock结果),建议把JWT校验逻辑抽成独立依赖,方便Mock:

  1. 定义JWT校验接口与实现类:
interface JwtValidator {
    fun isTokenValid(token: String): Boolean
}

class DefaultJwtValidator : JwtValidator {
    override fun isTokenValid(token: String): Boolean {
        return try {
            val jwt = JWT(token)
            val isExpired = jwt.isExpired(0)
            Timber.d("Token ${if (isExpired) "expired" else "valid"} [${jwt.details()}]")
            !isExpired
        } catch (e: DecodeException) {
            Timber.w(e, "Invalid auth token.")
            false
        }
    }
}
  1. 修改AuthToken类,注入JwtValidator:
@JsonClass(generateAdapter = true)
data class AuthToken(
    @Json(name = "token") val access: String,
    @Json(name = "refreshToken") val refresh: String,
    private val jwtValidator: JwtValidator = DefaultJwtValidator() // 默认使用真实实现
) {
    fun isAccessTokenValid(): Boolean = jwtValidator.isTokenValid(access)
    fun isRefreshTokenValid(): Boolean = jwtValidator.isTokenValid(refresh)
}
  1. 测试时Mock JwtValidator:
val mockValidator = mockk<JwtValidator>()
val authToken = AuthToken("accessToken", "refreshToken", mockValidator)
every { mockValidator.isTokenValid("accessToken") } returns true
every { mockValidator.isTokenValid("refreshToken") } returns false

这种方式既能验证AuthToken的方法逻辑是否正确调用了校验器,又能避免执行真实的JWT解析代码。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 00:26:15