【问题标题】:Using capture and mocks to unit test a class使用捕获和模拟对类进行单元测试
【发布时间】:2021-07-17 11:45:58
【问题描述】:

我正在尝试对以下类进行单元测试:

class UserProfileDetailsAnalyticUseCaseImp @Inject constructor(private val analyticsProvider: AnalyticsProvider) : UserProfileDetailsAnalyticUseCase {
    override fun execute(cdsCustomer: CDSCustomer) {
        with(analyticsProvider) {
            log(AnalyticEvent.UserId(cdsCustomer.id.toString()))
            log(AnalyticEvent.UserEmail(cdsCustomer.email))
        }
    }
}

这是我的单元测试:

class UserProfileDetailsAnalyticUseCaseImpTest {

    private lateinit var userProfileDetailsAnalyticUseCaseImp: UserProfileDetailsAnalyticUseCaseImp
    private val analyticsProviders: AnalyticsProvider = mock()


    @Before
    fun setUp() {
        userProfileDetailsAnalyticUseCaseImp = UserProfileDetailsAnalyticUseCaseImp(analyticsProviders)
    }

    @Test
    fun `should send analytic event`() {
        // Arrange
        val cdsCustomer = CDSCustomer(
            id = Random.nextInt(0, 100000),
            email = UUID.randomUUID().toString())

        val userIdCapture= argumentCaptor<AnalyticEvent.UserId>()
        val userEmailCapture= argumentCaptor<AnalyticEvent.UserEmail>()

        // Act
        userProfileDetailsAnalyticUseCaseImp.execute(cdsCustomer)

        // Assert
        verify(analyticsProviders, atLeastOnce()).log(userIdCapture.capture())
        verify(analyticsProviders, atLeastOnce()).log(userEmailCapture.capture())
    
        assertThat(userIdCapture.firstValue.userId).isEqualTo(cdsCustomer.id.toString())
        assertThat(userEmailCapture.firstValue.email).isEqualTo(cdsCustomer.email)
    }
}

我得到的错误如下:

AnalyticEvent$UserId cannot be cast to AnalyticEvent$UserEmail

我怀疑因为被测类正在为每个log 方法创建一个新对象,所以对于单元测试中已验证的方法,它们不会相同

log(AnalyticEvent.UserId(cdsCustomer.id.toString()))

将创建一个新的AnaltyicEvent.UserId 并仅用于相同的AnalyticProvider 模拟

非常感谢您的任何建议

【问题讨论】:

    标签: android unit-testing kotlin


    【解决方案1】:

    ArgumentCaptorthe documentation 中,我们可以读到:

    这个实用程序类不做任何类型检查。通用的 签名只是为了避免在您的代码中强制转换。

    此外,用于收集捕获参数的CapturingMatcher有一个匹配所有对象的方法:

    public boolean matches(Object argument) {
        return true;
    }
    

    这意味着这是正常行为,即使我们指定具体类型的捕获器,它也会记录所有传递的参数。 当然,所有这些参数都必须继承自同一个基类,因为在其他情况下capture 方法会导致编译错误。

    所以,你的两个俘虏都记录了两个论点。

    要为您的测试修复类转换异常,您可以为电子邮件声明 secondValue

    assertThat(userEmailCapture.secondValue.email).isEqualTo(cdsCustomer.email) 
    

    您也可以停止使用参数捕获器并简单地验证 log 方法的调用。

    verify(analyticsProviders).log(AnalyticEvent.UserId(cdsCustomer.id.toString()))
    verify(analyticsProviders).log(AnalyticEvent.UserEmail(cdsCustomer.email))
    

    【讨论】:

    • 一个问题是在验证方法中将创建一个新的AnalyticEvent.UserId(...)。在生产代码中会有所不同。由于我们正在创建一个新的AnalyticEvent.UserId(..),所以当我运行测试时会失败。想要的AnalyticEvent$UserId@446a5cd0 和实际的AnalyticEvent$UserId@3b663b98
    • Mockito 在验证过程中使用equals 方法。当您的 AnalyticEvents 是数据类或已正确实现 equals 方法时,它是否是对象的同一实例并不重要。测试应该通过
    • 我的AnalyticEvent 类是密封类。有没有其他方法可以在不覆盖该类中的 equals 方法的情况下进行测试?
    • 如果您的AnalyticEvent 类被密封,那么您的UserIdUserEmail 应该是扩展它的数据类,您不必担心equalshashCode 方法. data class UserEmail(val email: String) : AnalyticEvent()
    • 这看起来是一个正确的答案,数据类的想法/解决方法应该可以正常工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多