jmockit / jmockit1

Advanced Java library for integration testing, mocking, faking, and code coverage
Other
461 stars 239 forks source link

java.lang.ClassCastException: java.lang.Class cannot be cast to {Mock method return type} #717

Open fdz-nelson-branco opened 2 years ago

fdz-nelson-branco commented 2 years ago

Please provide the following information:

Saljack commented 2 years ago

Do you have a code example? What do you use MockUp or @Injectable etc?

fdz-nelson-branco commented 2 years ago

I'm in an effort of fixing this flaky symptom over a couple of tests. It happens on Mocked and Injectable references, much more frequent on Injectables, I've a couple of comments on the code stating that they had mocked it using Mockups due to issues using Expectations but with no further details.

One of the cases we can put it like this:

public void someTestMoethod(@Mocked final Dependency1 dependency1) {
    final long id = dependency1.getId(); // <<--- rarely it throws "java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.Long"
}

It occurs both with or without an expectation. The machine has it's CPU exhausted running the test in parallel, process forked per class, and executing sequentially over the class.

I'm looking into the source code trying to make sense of it. I spotted this exception (on a passing test) on a scheduled task that continues after the test case, this I think to be related to the mocking being "cleanup" upon the test case finishes. Also, as far as I understood, since the mocking structures aren't synchronized, we must not create expectations using multiple threads, at least without the proper synchronization.

Even so, I still can make sense of it on the case shown above.

Much appreciated your help, even if you can only guide me on where to look for on the code.

fdz-nelson-branco commented 2 years ago

Narrowing down a little bit more with additional details:

interface Dependency1Interface {
    Identifier getId();
}

class Dependency1 implements Dependency1Interface {}

public void someTestMethod(@Mocked final Dependency1 dependency1) {
    final Identifier id = dependency1.getId(); // <<--- rarely it throws "java.lang.ClassCastException: java.lang.Class cannot be cast to com.foo.Identifier"
}

So far I managed to reverse engineer the getId() redefined method into the following:

public getId()Ljava/foo/Identifier;
    ALOAD 0
    LDC 1025
    LDC "com/foo/Dependency1Interface"
    LDC "getId()Lcom/foo/Identifier;"
    ACONST_NULL
    LDC 0
    ACONST_NULL
    INVOKESTATIC mockit/internal/expectations/RecordAndReplayExecution.recordOrReplay (Ljava/lang/Object;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/Object;)Ljava/lang/Object;
    CHECKCAST com/foo/Identifier
    ARETURN
    MAXSTACK = 7
    MAXLOCALS = 1

Seems to me that this CHECKCAST com/foo/Identifier is the source of the exception. If that's true, that mean the Class instance is being returned by the RecordAndReplayExecution.recordOrReplay.

fdz-nelson-branco commented 2 years ago

Adding up on the findings...

I spotted that within certain circumstances, like the case where a task continues to run after the end of the test case, the RecordAndReplayExecution.recordOrReplay returns Void.class, so, this Void.class's must be on the root of the exception being thrown, now, on the getId() scenario I can't figure out why it fails from time to time...

zbflcy commented 1 year ago

Why hasn't this problem been fixed yet?