Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MockedConstruction not autoclosed #2281

Closed
snv opened this issue Apr 30, 2021 · 6 comments
Closed

MockedConstruction not autoclosed #2281

snv opened this issue Apr 30, 2021 · 6 comments

Comments

@snv
Copy link

snv commented Apr 30, 2021

Hi,
i am using jUnit5 5.7.1 and Mockito 3.9.0, still on Java 8, starting the tests from IntelliJ IDEA 2020.3.4

I have two testclasses (marked with @ExtendWith({MockitoExtension.class})), which each have a test-method, which mocks the construction of MOCKED_CLASS via @Mock as parameter.

The second test fails though, with

org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [org.mockito.MockedConstruction<MOCKED_CLASS> arg1] in method [public void SECONDLY_INVOCED_TEST_METHOD(org.mockito.MockedConstruction<MOCKED_CLASS>) throws java.lang.Exception]:
For MOCKED_CLASS, static mocking is already registered in the current thread

To create a new mock, the existing static mock registration must be deregistered
at org.junit.jupiter.engine.execution.ExecutableInvoker.resolveParameter(ExecutableInvoker.java:239)
at org.junit.jupiter.engine.execution.ExecutableInvoker.resolveParameters(ExecutableInvoker.java:183)
at org.junit.jupiter.engine.execution.ExecutableInvoker.resolveParameters(ExecutableInvoker.java:144)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:96)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: org.mockito.exceptions.base.MockitoException:
For com.oda.core.domain.production.customerImport.returns.ImportReturnsTransmissionFile, static mocking is already registered in the current thread

To create a new mock, the existing static mock registration must be deregistered
at org.mockito.junit.jupiter.MockitoExtension.resolveParameter(MockitoExtension.java:198)
at org.junit.jupiter.engine.execution.ExecutableInvoker.resolveParameter(ExecutableInvoker.java:216)
... 50 more

I can prevent that from happening, if i add a call to ScopedMock#closeOnDemand on the injected MockedConstruction<MOCKED_CLASS> at the end of the first invoced test method.
So my conclusion is that the mocked construction was not auto-closed at the end of the method's scope, which is what i expected @Mock in combination with @ExtendWith({MockitoExtension.class}) to do.

Otherwise @Mock would be unusable for this, and i would have to use the try-with-ressouces syntax, to make sure the MockedConstruction actually gets released, even if the test fails (which is the workaround i will use for now).

@wisecodecraft
Copy link

Is this still an open issue? Potentially interested in helping out here.

@TimvdLippe
Copy link
Contributor

@wisecodecraft Yes feel free to send us a PR with a fix 😄

@snv
Copy link
Author

snv commented Jun 15, 2021

Thanks for looking into this.

I just checked and this still happens with updated Versions:
Java 11
jUnit 5.7.2
mockito 3.11.1

Related to that (but maybe separate), would be the missing ability to configure the injected MockedConstruction:

  • With an injected MockedStatic i can just configure mock-Responses with the usual when(...)-Syntax.
  • With a self-made MockedConstruction i can supply a configuration at creation with a MockInitializer
  • To be able to actually configure an injected MockedConstruction, it would need an accessor to assign it a MockInitializer to use on newly created Mocks.

@temp-droid
Copy link
Contributor

temp-droid commented Oct 5, 2021

Hi @snv ,

From the javadoc it seems clear Mockito expects you to handle it:

org.mockito.Mockito @Incubating 
public static <T> org.mockito.MockedConstruction<T> mockConstruction(Class<T> classToMock)

Creates a thread-local mock controller for all constructions of the given class. 
The returned object's MockedConstruction.close() method must be called upon completing the test 
or the mock will remain active on the current thread.  

I'm not sure to have fully grabbed your way of using it (particularly your @Mock usage), but could @AfterEach or a similar annotation help you?
I am not used to that notation but I think I figured out how the @Mock annotation comes in place. AfterEach won't be helpful here since you don't have a field to rely on.
I'll try to understand why the afterEach of MockitoExtension doesn't process those mocks.

    @Override
    @SuppressWarnings("unchecked")
    public void afterEach(ExtensionContext context) {
        context.getStore(MOCKITO).remove(MOCKS, Set.class).forEach(mock -> ((MockedStatic<?>) mock).closeOnDemand());
        context.getStore(MOCKITO).remove(SESSION, MockitoSession.class)
                .finishMocking(context.getExecutionException().orElse(null));
    }

@snv
Copy link
Author

snv commented Oct 5, 2021

Hi,

i hoped to use it the same way as a MockedStatic instance, letting the test-environment inject me what i need.

Somewhat like that:

@Test
public void testing_for_something(@Mock MockedConstruction<WhatEver> whatEverConstruction){
  // arrange
  //add configuration to whatEverConstruction

  // act
  //call something that instantiates whatEver

  // assert
  //stuff
}

When using this pattern with MockedStatic it does work, but sadly not with MockedConstruction, which has to be used in a try-with-ressources block.
Since i often do not even use the MockedConstruction-instance (not the mocked instances created by the mockedConstruction-instance), this leads to code-inspection warnings, and is just very boilerplaty-verbose and even prevents me from separating the arrange- and act-parts inside the test-code.
This sabotages readability a lot:

@Test
public void testing_for_something(){
  // arrange  
  //configure other stuff
  try (MockedConstruction ignored = mockConstruction(
    whatEver.class,
    (mock, ctx) -> {
       //add configuration to mock
    }
  ) {
    // act
    //call something that instantiates WhatEver
 }  

  // assert
  //stuff
}

Since https://javadoc.io/static/org.mockito/mockito-core/3.12.4/org/mockito/MockitoAnnotations.html#openMocks-java.lang.Object- returns AutoCloseable i kinda assumed that the instances get closed after each test.

I know this is marked as an incubating feature, so i hope this is useful feedback.

temp-droid pushed a commit to temp-droid/mockito that referenced this issue Oct 5, 2021
MockedConstruction should behave like MockedStatic when we use @mock on it.
temp-droid pushed a commit to temp-droid/mockito that referenced this issue Oct 6, 2021
MockedConstruction should behave like MockedStatic when we use @mock on it.
@snv
Copy link
Author

snv commented Oct 6, 2021

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants