mockk icon indicating copy to clipboard operation
mockk copied to clipboard

Bug: ClassCastException when using relaxed mock of generic type

Open rdgoite opened this issue 6 years ago • 24 comments

Prerequisites

Please answer the following questions for yourself before submitting an issue.

  • [x] I am running the latest version
  • [x] I checked the documentation and found no answer
  • [x] I checked to make sure that this issue has not already been filed

Description

I might have run into a MockK limitation trying to use relaxed mock of a generic type (particularly Spring's JpaRepository). When using such mock object, my test throws a ClassCastException at the point where the test code calls the method of the mock object that returns an object using the type parameter.

For example, if have the following types,

interface BaseRepository<T> {
    fun save(obj: T): T
}

interface BookRepository: BaseRepository<Book>

and I declare a relaxed mock for BookRepository, the test throws a ClassCastException during the invocation of save(book).

Steps to Reproduce

I have prepared a demo code base to illustrate this issue. Please check the last 3-4 commits as I experimented a bit to confirm that this issue doesn't only affect Spring's JpaRepository but in general, any type (interface) with similar declaration.

rdgoite avatar Jun 20 '19 21:06 rdgoite

same problem here (mockk 1.9.3). Small example code to reproduce the problem:

interface BaseRepository<T> {
    fun save(obj: T): T
}

interface IntRepository: BaseRepository<Int>

fun main(args: Array<String>) {
    val a = mockk<IntRepository>(relaxed = true)
    a.save(1)
}

Results in:

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.Integer

robstoll avatar Aug 15 '19 20:08 robstoll

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. If you are sure that this issue is important and should not be marked as stale just ask to put an important label.

stale[bot] avatar Oct 14 '19 20:10 stale[bot]

I am experiencing the same issue. I am trying to mock the following function:

inline fun <reified T, reified U> post(uri: String, body: T): U? = webClient
        .post()
        .uri(uri)
        .body(Mono.just(body), T::class.java)
        .retrieve()
        .bodyToMono(U::class.java)
        .block()

like this:

every { httpClient.post<Map<String, Any>, User>(any(), hashMapOf()) } returns createUser(email = email)

but get the same error as above:

java.lang.Object cannot be cast to User. I am using version 1.9.3 of mockk and a relaxed mock of the httpClient.

Oddly enough, mocking this function works fine:

inline fun <reified T> getMono(uri: String): Mono<T> = webClient
        .get()
        .uri(uri)
        .retrieve()
        .bodyToMono(T::class.java)
        .block()

Is there any update on this?

mimozell avatar Feb 11 '20 06:02 mimozell

I am facing the same issue. I am using a Result wrapper for my network models.

sealed class Result<out R> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Failure) : Result<Nothing>()
}

However, I am getting;

java.lang.ClassCastException: java.lang.Object cannot be cast to mypath.data.MyResponse

nuhkoca avatar Mar 06 '20 09:03 nuhkoca

Try to use hint. check docs

пт, 6 мар. 2020 г., 10:54 Nuh Koca [email protected]:

I am facing the same issue. I am using a result wrapper for my network models.

sealed class Result<out R> { data class Success<out T>(val data: T) : Result<T>() data class Error(val exception: Failure) : Result<Nothing>() }

However I am getting;

java.lang.ClassCastException: java.lang.Object cannot be cast to mypath.data.MyResponse

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/mockk/mockk/issues/321?email_source=notifications&email_token=ABIBXDCOKW4TGW5467MUOWLRGDB6HA5CNFSM4HZ6V6O2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEOAYQSQ#issuecomment-595691594, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABIBXDH5GI2W4JXSGPFYDNTRGDB6HANCNFSM4HZ6V6OQ .

oleksiyp avatar Mar 06 '20 09:03 oleksiyp

@oleksiyp thanks it seems useful however I am having one question.

Because of this generic type, whenever I want to access a val of my data class, I am getting;

io.mockk.MockKException: no answer found for: MyResponse(myResponse#2).getMyVal() How can I fix this with hint? I am using it like;

coEvery {
            service.fetchPolygons(
                any(),
                capture(maxSpeedSlot),
                any(),
                any()
            ).hint(Result::class)
        } answers {
            myResponse
        }

but it didn't work. In addition, I'd like to assert multiple values. In case of that, should I use multiple hints?

Thanks!

nuhkoca avatar Mar 06 '20 10:03 nuhkoca

Hint should be before call

пт, 6 мар. 2020 г., 11:26 Nuh Koca [email protected]:

@oleksiyp https://github.com/oleksiyp thanks it seems useful however I am having one question.

Because of this generic type, whenever I want to access a val of my data class, I am getting;

io.mockk.MockKException: no answer found for: MyResponse(myResponse#2).getMyVal() How can I fix this with hint? I am using it like;

coEvery { service.fetchPolygons( any(), capture(maxSpeedSlot), any(), any() ).hint(Result::class) } answers { myResponse }

but it didn't work. Thanks!

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mockk/mockk/issues/321?email_source=notifications&email_token=ABIBXDCDAQJ6QD5CFFGEJVLRGDFU3A5CNFSM4HZ6V6O2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEOA3TKY#issuecomment-595704235, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABIBXDCQMSKZ6GGNBMWBBVTRGDFU3ANCNFSM4HZ6V6OQ .

oleksiyp avatar Mar 06 '20 10:03 oleksiyp

Ok, it is working. So do I have to use hint per assertion? I mean I'd like to assert 3 conditions so that I need to create coEvery per condition, right?

nuhkoca avatar Mar 06 '20 11:03 nuhkoca

I am facing the same issue. I am using a Result wrapper for my network models.

sealed class Result<out R> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Failure) : Result<Nothing>()
}

However, I am getting;

java.lang.ClassCastException: java.lang.Object cannot be cast to mypath.data.MyResponse

hey @nuhkoca did you manage to resolve that issue?

yavski avatar Apr 07 '21 15:04 yavski

@yavski for most cases I fixed this switching to Coroutines and wrapping Result in Flow instead. Then I am able to easily test my flow with cashapp's turbine. Other than that, it wasn't working last time but I will try and let you know.

https://github.com/cashapp/turbine

nuhkoca avatar Apr 07 '21 16:04 nuhkoca

Any updates on this by any chance? I have hundreds of tests failing because of this issues so unfortunately using hint() on all of them would be an enormous effort. Type inference was somehow working on Java 8 (I only have this issue when switching to Java 11). This is possibly related to https://github.com/mockk/mockk/issues/606 as well?

marcosalis avatar May 11 '21 15:05 marcosalis

I solved it as below every { repository.save<ReturnTypecClass>(any()) } returns ReturnTypeClass

liquidjoo avatar May 17 '21 03:05 liquidjoo

I am wrong assuming that the intention when we use mockk(relaxed = true) is to avoid writing a default answer where every call is done?

gerardoepitacio avatar May 25 '21 18:05 gerardoepitacio

@oleksiyp is there anything we can do to get a workaround for this by any chance? On large codebases this is causing hundreds of tests failures which would be really hard to fix one by one with hint. Thank you in advance for looking into it! 🙏

marcosalis avatar Jun 01 '21 09:06 marcosalis

any update on this??

egek92 avatar Jun 16 '21 08:06 egek92

Hey guys, it's blocking me and my team from updating to Android Studio 4.2 since it requires Java 11 and our codebase has a lot of tests in mockk that throw this exception on Java 11. Could you give an estimate when it's going to be fixed?

konaire avatar Jul 14 '21 07:07 konaire

Hello, any update on this? 🙏

jstarzynski avatar Jul 14 '21 07:07 jstarzynski

Hey guys, a quick light on this issue

I was facing the problem when I was writing unit tests where one of mocked parameters was called at my ViewModel class.

I fixed this by creating mocks with every/coEvery of the mocked object functions that were called at the time of the crash.

diegocunhawarren avatar Aug 20 '21 18:08 diegocunhawarren

As it is quite common to test with the JpaRepository pattern, this issue is quite impactful.

Jelledb avatar Apr 28 '22 08:04 Jelledb

Still have this issue even if relaxed == false. But now i have Result<Boolean> instead of Result<MyClass>

lebedynskyi avatar Aug 01 '22 19:08 lebedynskyi

Not able to mock repository beans, get this error with save calls. Tried to use hint and relaxed, still same problem.

fdhuseynov avatar Jan 11 '23 08:01 fdhuseynov

I also have the same issue. It's very unfortunate.

DasOhmoff avatar Jul 14 '23 10:07 DasOhmoff