openrewrite / openrewrite/rewrite-testing-frameworks

Issue when MockedStatic field is defined with an inline Mockito.mockStatic initialiser

Open
#1,127 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Java
Stars
100
Forks
105
Avg merge
2h 25m
Merged PRs (30d)
9

Description

What version of OpenRewrite are you using?

I am using

  • Maven plugin: org.openrewrite.maven:rewrite-maven-plugin v6.44.0
  • org.openrewrite.recipe:rewrite-spring v6.35.0
  • org.openrewrite.recipe:rewrite-testing-frameworks v3.42.0 (pulled in transitively by rewrite-spring)

The bug lives in org.openrewrite.java.testing.mockito.CloseUnclosedStaticMocks, in rewrite-testing-frameworks. It is reached transitively via:

org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5spring-boot-31.yml's org.openrewrite.java.testing.mockito.Mockito4to5Onlyorg.openrewrite.java.testing.mockito.Mockito1to4Migrationorg.openrewrite.java.testing.mockito.CloseUnclosedStaticMocks (last recipe in Mockito1to4Migration's recipeList, per mockito.yml).

How are you running OpenRewrite?

I am using the Maven plugin (rewrite-maven-plugin:run), on a single-module project, running a declarative recipe chain that includes org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5.

<plugin>
  <groupId>org.openrewrite.maven</groupId>
  <artifactId>rewrite-maven-plugin</artifactId>
  <version>6.44.0</version>
  <configuration>
    <activeRecipes>
      <recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5</recipe>
    </activeRecipes>
  </configuration>
</plugin>

What is the smallest, simplest way to reproduce the problem?

package com.example;

import org.mockito.MockedStatic;
import org.mockito.Mockito;

class MyTest {
    private static final MockedStatic<CryptoUtil> MOCKED_CRYPTO_UTILS = Mockito.mockStatic(CryptoUtil.class);
}

Any test class that declares a MockedStatic field with an inline Mockito.mockStatic(...) initializer directly in the class body (as opposed to assigning it in a @BeforeAll/@BeforeEach lifecycle method) triggers the crash:

Root cause (confirmed by reading CloseUnclosedStaticMocks's source from rewrite-testing-frameworks-3.42.0-sources.jar): its visitBlock() override fires on any J.Block, but a class body is also represented as a J.Block in the LST, just like a method body. The recipe guards against lifecycle-method blocks (insideLifecycleMethod()), but not against the class-body block itself. So it mistakes this field declaration for an "unclosed local variable" inside a block, and attempts to wrap it in a try (#{any()}) {} template — which isn't valid for a field declaration (its modifiers, e.g. private static final, aren't compatible with a try-with-resources resource declaration). The resulting JavaTemplate.apply() call generates 2 statements instead of the expected 1 replacement statement, and throws.

Note that the recipe's separate assignment-based path (visitAssignment / DeclareMockVarAndClose, used when mockStatic() appears as VAR = Mockito.mockStatic(X.class); rather than as an inline field initializer) works correctly, including correctly recognizing a pre-existing manual .close() call as already handling cleanup. The bug is specific to the inline-field-initializer form.

What did you expect to see?

Since the field's MockedStatic is already closed manually elsewhere (e.g. in a matching @AfterAll), I'd expect the recipe to make no change, e.g.:

package com.example;

import org.mockito.MockedStatic;
import org.mockito.Mockito;

class MyTest {
    private static final MockedStatic<CryptoUtil> MOCKED_CRYPTO_UTILS = Mockito.mockStatic(CryptoUtil.class);

    @AfterAll
    static void afterAll() {
        MOCKED_CRYPTO_UTILS.close();
    }
}

What did you see instead?

At minimum, I'd expect the recipe to not crash the whole run — e.g. it could correctly detect the existing .close() call the way it already does for the assignment-based form, or leave inline-initialized class-body fields alone entirely if it can't safely rewrite them.

What is the full stack trace of any errors you encountered?

[ERROR] Failed to execute goal org.openrewrite.maven:rewrite-maven-plugin:6.44.0:run (default-cli) on project AppFunctionalTests: Execution default-cli of goal org.openrewrite.maven:rewrite-maven-plugin:6.44.0:run failed: Error while visiting app_name/AppFunctionalTests/filename.java: java.lang.IllegalArgumentException: Expected a template that would generate exactly one statement to replace one statement, but generated 2. Template:
[ERROR] try(__P__.<org.mockito.MockedStatic<Unknown>>/*__p0__*/p()) {}
[ERROR] Substitutions:
[ERROR] Substitutions(code=try(#{any()}) {}, genericTypes=[], parameters=[private static final MockedStatic<CryptoUtil> MOCKED_CRYPTO_UTILS = Mockito.mockStatic(CryptoUtil.class)], propertyPlaceholderHelper=org.openrewrite.internal.PropertyPlaceholderHelper@72eca5c5, typeVariables=[])
[ERROR] Statement:
[ERROR] private static final MockedStatic<CryptoUtil> MOCKED_CRYPTO_UTILS = Mockito.mockStatic(CryptoUtil.class)
	at org.openrewrite.java.JavaTemplate.apply(JavaTemplate.java:...)
	at org.openrewrite.java.testing.mockito.CloseUnclosedStaticMocks$CloseUnclosedStaticMocksVisitor.toTryWithResource(CloseUnclosedStaticMocks.java:...)
	at org.openrewrite.java.testing.mockito.CloseUnclosedStaticMocks$CloseUnclosedStaticMocksVisitor.visitBlock(CloseUnclosedStaticMocks.java:...)
	...

Are you interested in contributing a fix to OpenRewrite?

No.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in org.openrewrite.java.testing.mockito.CloseUnclosedStaticMocks, especially the visitBlock() path and its lifecycle-method guard, using the inline MockedStatic field reproducer in the issue. Add regression coverage for this class-body case and verify the recipe no longer throws while preserving the field when cleanup is already handled, then run the relevant rewrite-testing-frameworks tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
testing-qa
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.