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

Add JMockit's MockUp to Mockito migration #599

Open
wants to merge 49 commits into
base: main
Choose a base branch
from

Conversation

SiBorea
Copy link

@SiBorea SiBorea commented Sep 11, 2024

What's changed?

Add JMockit MockUp to Mockito

What's your motivation?

Checklist

  • [✅] I've added unit tests to cover both positive and negative cases
  • [✅] I've read and applied the recipe conventions and best practices
  • [✅] I've used the IntelliJ IDEA auto-formatter on affected files

Copy link
Contributor

@timtebeek timtebeek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great addition @SiBorea ! I've pushed up a small change and left two initial comments; I'd need a little more time for a full review, but this is looking very promising.

Tagging @tinder-dthomson and @shivanisky for additional review (if possible), since they have more experience with JMockit migrations.

@shivanisky
Copy link
Collaborator

Awesome for working on this!

@tinder-dthomson
Copy link
Contributor

@timtebeek @SiBorea hey folks! I am a bit unavailable at the moment as I'm currently caring for a one-week old newborn 😄

I'll try to take a look when I get some free time, but please don't depend on me for ongoing efforts for the time being!

@shivanisky
Copy link
Collaborator

shivanisky commented Sep 12, 2024

Congrats on new baby!

@shivanisky
Copy link
Collaborator

shivanisky commented Sep 12, 2024

Looking at the unit tests, we can't migrate Mockup directly to mock + when statements. Because Mockup is typically used to stub a subset of methods while the remaining methods behave as specified in the original class.

https://javadoc.io/doc/org.jmockit/jmockit/latest/mockit/MockUp.html

If we directly migrate to mock+when, then this will cause failures in all cases where a subset of methods are stubbed in the mockup. And this would be the most common outcome.

I think best to use spy for non static.

For static, private or final, may try PowerMockito - haven't tried this.

But even this doesn't work in all cases and isn't a direct migration, but may cover most of the cases. I haven't seen enough MockUps to know.

For direct migration Byte code injection/cglib may be needed. That would probably cover all the cases but it's a tough one.

Maybe even worth going through jmockit source code and replicating what they are doing - probably byte code injection.

I hope that the above makes sense.

@shivanisky
Copy link
Collaborator

shivanisky commented Sep 12, 2024

You could do it in the following way if you don't want to use byte code injection:

  1. If in the MockUp, only static methods, then use MockStatic as you have already
  2. If non static methods, use spy only and the standard Mockito when
  3. If a mix, use both above - not sure if it will work, but one can hope.

Will need to use doAnswer for multiline method bodies and anything that uses the method args ... as thenAnswer doesn't support void methods. Can use thenAnswer for non void, and doAnswer for void, or to simplify I think doAnswer supports both, may be better to just stick with that. Will need to handle the case for method params. This is very similar to migrating JMockit Delegate.

To understand for the full migration, why byte code injection is needed JMockit allows this:

new MockUp<Bla> {
    void bla() {
    // custom
   }
}


// so where deep in the dev code this instance is created 
Bla bla = new Bla();
bla.bla(); // this will run custom above 

And can't be done without bytecode injection. Not sure if PowerMockito support this.

@SiBorea
Copy link
Author

SiBorea commented Sep 12, 2024

There will be a possibility that multiple new object statements after MockUp. Using spy may have to insert code every occurrence. And I suppose most likely those stubbed methods will not be called (at lease in my project). MockConstruction is better under this circumstance.
Nice suggestion to use doAnswer for both void and non void methods, will try.

Powermock has been inactive for years, and has compatibility issues about JDK17 and high version of Mockito. Don't feel like a good solution.
Furthermore, Mockito's claim about mocking private/final method sound to me.
https://github.com/mockito/mockito/wiki/Mockito-And-Private-Methods
Do you think we should support these? It's a JMockit to Mockito 5 recipe.

@SiBorea
Copy link
Author

SiBorea commented Sep 12, 2024

Do we have byte code injection example in OpenRewrite? Haven't seen it before

@shivanisky
Copy link
Collaborator

shivanisky commented Sep 12, 2024 via email

@SiBorea
Copy link
Author

SiBorea commented Sep 13, 2024

I think PowerMockito is the same as PowerMock, quote Basically, PowerMock provides a class called "PowerMockito" for creating mock/object/class and initiating verification, and expectations, everything else you can still use Mockito to setup and verify expectation (e.g. times(), anyInt()).

I guess people copied and pasted the MockUp code from somewhere in my case. I saw MockUp everywhere and no Expectation.

SiBorea and others added 3 commits September 13, 2024 15:54
…UpToMockitoTest.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…UpToMockitoTest.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…UpToMockitoTest.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Copy link
Collaborator

@shivanisky shivanisky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests are looking very good. Just a few more comments - the main one being removal of the withSettings().defaultAnswer(CALLS_REAL...) as uneeded and to shorten some variable names and remove uneeded FQN, unless you think the FQN is needed. I'll take a look at anything remaining on my end.

@SiBorea
Copy link
Author

SiBorea commented Oct 6, 2024

I leave the fqn for those inner static classes from which other than the test class. But it's ok to remove, since such usage is very little.

@SiBorea
Copy link
Author

SiBorea commented Oct 6, 2024

This CI build failed for PowerMockitoMockStaticToMockitoTest. Could you check? @timtebeek

import static org.openrewrite.java.tree.Flag.Private;
import static org.openrewrite.java.tree.Flag.Static;

public class JMockitMockUpToMockito extends Recipe {
Copy link
Collaborator

@shivanisky shivanisky Oct 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a look at the overall design, I'm wondering if it would be much better to reuse the existing code in JMockitBlockToMockito, and add MockUp in the Preconditions, and then in JMockitBlockRewriter if the block type is a mockup, call much of the code of this class to rewrite the method by passing the the block, visitor and context. This however, would make it difficult to handle tearing down of any mock constructors if the teardown method is before the setup method so possibly a two cycle recipe would be needed, however it would probably reduce the number of lines of code due to the reuse.

class Bla {  
   @Before
   void setup() {
       // do some migration
   }

   @After
   void teardown() {
       // do some migration based on migration of setup method above - what if this teardown method is before the setup above
   }
}

Also build needs to be green before submitting for review as the main build is green.

Copy link
Author

@SiBorea SiBorea Oct 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it be too complicated integrating mockUp into JMockitBlockToMockito?

I can see the tests of PowerMockitoMockStaticToMockitoTest and other recipes failed and cause the build red.

image

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cloned the latest code in main and built. Those cases are failed too. @timtebeek are they expected?

@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
// Handle @Before/@BeforeEach mockUp
Set<J.MethodDeclaration> mds = TreeVisitor.collect(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TreeVisitor.collect is marked @Incubating and is an experimental feature and may have breaking changes, is it possible to avoid this one?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the final solution's code will very likely the same as TreeVisitor.collect()'s.

@shivanisky
Copy link
Collaborator

To me the functionality looks good, just have started to look at the main code:

  1. Junit 5 BeforeEach/BeforeAll is not handled (can be done in separate PR)
  2. Could the mockito-core 5.x dependency replace the current mockito-core 3.x dependency in the parser classpath?

It's quite a lot of code to review so adding back @timtebeek to the review process as the MockUp migration in unit tests looks right

@openrewrite openrewrite deleted a comment from github-actions bot Oct 14, 2024
SiBorea and others added 2 commits October 24, 2024 10:23
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

4 participants