Using capture and mocks to unit test a class

Viewed 245

I am trying to unit test the following class:

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))
        }
    }
}

And this is my unit test:

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)
    }
}

The error I get is the following:

AnalyticEvent$UserId cannot be cast to AnalyticEvent$UserEmail

I am suspecting that because class under test is creating a new object for each log method they will not be the same for the verified methods in the unit test

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

As a new AnaltyicEvent.UserId will be created and just for the same AnalyticProvider mock

Many thanks for any suggetions

1 Answers

In the documentation of ArgumentCaptor we can read that:

This utility class doesn't do any type checks. The generic signatures are only there to avoid casting in your code.

Moreover CapturingMatcher which is used for collecting captured arguments has a method which matches all objects:

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

It means that it is normal behaviour and even when we specify concrete type of captor it will record all arguments passed. Of course all these arguments have to inherit from the same base class because in other case capture method will cause compilation error.

So, both your captors record two arguments.

To fix class cast exception for your test you can assert secondValue for email.

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

You can also stop using argument captors and simply verify invocations of log method.

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