mockk / mockk

mocking library for Kotlin
https://mockk.io
Apache License 2.0
5.45k stars 346 forks source link

Allow suppressing "Failed to set backing field" OR fail test #1291

Open cgm-aw opened 2 months ago

cgm-aw commented 2 months ago

Introduction

Hi everyone,

I encountered a behavior of mockk, where, if the backing field has a different type than the getter, a warning is printed to the logs but the test continues and passes. This seems weird to me - I would either expect the test to fail or no warning message. My suggestion: Add a parameter to the mockk() function to be able to suppress the warning, and if suppression is not active, the test will fail (or the other way around).

I'm aware of this: https://mockk.io/#property-backing-fields With the solution presented there, everything works fine. However, if you do not use this mechanism, you will just get confusing error messages and probably an inconsistent behavior.

Prerequisites

Expected Behavior

Either a broken test or no log message

Current Behavior

A warning message is printed, including a stack trace, but the test passes

Steps to Reproduce

Create this class (this is a Java class!)

import java.util.Optional;

public class ContainerClass {
    private Optional<String> someField;
    public String getSomeField() {
        return someField.orElse("empty");
    }
}

Create this Kotlin test:

    @kotlin.test.Test
    fun reproduceFailedToSetBackingField() {
        val testObject = mockk<ContainerClass>()

        every { testObject.someField } returns "mockValue"

    }

You will see this log entry:

2024-08-29 07:37:00,610 [main] WARN  i.m.i.r.states.StubbingAwaitingAnswerState - Failed to set backing field (skipping)
java.lang.IllegalArgumentException: Can not set java.util.Optional field test.ContainerClass.someField to java.lang.String
[...]

Using propertyType yields the correct behavior: every { testObject.someField } propertyType Optional::class answers { "mockValue" }

Context

Stack trace

// -----------------------[ YOUR STACK STARTS HERE ] -----------------------
2024-08-29 07:37:00,610 [main] WARN  i.m.i.r.states.StubbingAwaitingAnswerState - Failed to set backing field (skipping)
java.lang.IllegalArgumentException: Can not set java.util.Optional field test.ContainerClass.someField to java.lang.String
    at java.base/jdk.internal.reflect.FieldAccessorImpl.throwSetIllegalArgumentException(FieldAccessorImpl.java:228)
    at java.base/jdk.internal.reflect.FieldAccessorImpl.throwSetIllegalArgumentException(FieldAccessorImpl.java:232)
    at java.base/jdk.internal.reflect.MethodHandleObjectFieldAccessorImpl.set(MethodHandleObjectFieldAccessorImpl.java:115)
    at java.base/java.lang.reflect.Field.set(Field.java:836)
    at io.mockk.InternalPlatformDsl.dynamicSetField(InternalPlatformDsl.kt:182)
    at io.mockk.impl.recording.states.StubbingAwaitingAnswerState.assignFieldIfMockingProperty(StubbingAwaitingAnswerState.kt:68)
    at io.mockk.impl.recording.states.StubbingAwaitingAnswerState.access$assignFieldIfMockingProperty(StubbingAwaitingAnswerState.kt:9)
    at io.mockk.impl.recording.states.StubbingAwaitingAnswerState$assignFieldIfMockingProperty$1.invoke(StubbingAwaitingAnswerState.kt:52)
    at io.mockk.impl.recording.states.StubbingAwaitingAnswerState$assignFieldIfMockingProperty$1.invoke(StubbingAwaitingAnswerState.kt:51)
    at io.mockk.impl.stub.AnswerAnsweringOpportunity.notifyFirstAnswerHandlers(AnswerAnsweringOpportunity.kt:30)
    at io.mockk.impl.stub.AnswerAnsweringOpportunity.provideAnswer(AnswerAnsweringOpportunity.kt:21)
    at io.mockk.MockKStubScope.answers(API.kt:2292)
    at io.mockk.MockKStubScope.returns(API.kt:2296)
    at test.CustomersPojoTest.reproduceFailedToSetBackingField(CustomersPojoTest.kt:102)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:728)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
    at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
    at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
    at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:218)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:214)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:139)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:198)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85)
    at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47)
    at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:63)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
// -----------------------[ YOUR STACK TRACE ENDS HERE ] -----------------------

Minimal reproducible code (the gist of this issue)

See above

PavlosTze commented 3 weeks ago

I'm also encountering the same issue. @Raibaz @zkkv @ any ideas?

bjpe commented 2 weeks ago

Hi, I also got the same problem today. @cgm-aw : Could you elaborate how you circumvented this issue using https://mockk.io/#property-backing-fields? Maybe you could just provide the adjusted test code. My current attempts do not work, unfortunately.

cgm-aw commented 2 weeks ago

Hi @bjpe

yes I added it to the description. Use this: every { testObject.someField } propertyType Optional::class answers { "mockValue" }

I also noticed that formatting in my post was a mess, I fixed that.

bjpe commented 2 weeks ago

Hi @cgm-aw , thank you very much! I also tried this, but it failed in my case because the field is not accessible (the mocked class is located in a library). Anyway, thanks a lot!