openrewrite / rewrite-testing-frameworks

OpenRewrite recipes that perform common Java testing migration tasks.
Apache License 2.0
77 stars 73 forks source link

Add `@MockitoSettings(strictness = ...)` based on how Mockito is used, not based on version alone #623

Closed Jenson3210 closed 1 month ago

Jenson3210 commented 1 month ago

As Mockito only changed default strictness as of 4, repositories using 3 should also be patched.

Jenson3210 commented 1 month ago

It seems there are some confusing docs online, because there are many ways to use mockito. After our slack discussion I did some investigations.

JUnit5

In a project with dependencies:

dependencies {
    testImplementation(platform("org.junit:junit-bom:5.10.0"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    testImplementation("org.mockito:mockito-core:<mockito_version>")
    testImplementation("org.mockito:mockito-junit-jupiter:<mockito_version>")
}

Given a java class

@ExtendWith(MockitoExtension.class)
class TestingTest {

    @Mock
    private List<String> mockList;

    @Test
    void testing() {
        // This would raise an UnnecessaryStubbingException as this mocked method is never called
        when(mockList.add("one")).thenReturn(true); // this won't get called
        System.out.println("Hello world!");
    }
}

This is throwing exception in following Mockito versions:

JUnit4

For JUnit4 it seems there is a difference between the way they are ran

In a project with dependencies

dependencies {
    testImplementation("junit:junit:4.12")
    testImplementation("org.mockito:mockito-core:<mockito_version>")
}

@RunWith(MockitoJUnitRunner.class)

@RunWith(MockitoJUnitRunner.class)
public class TestingTest {

    @Mock
    private List<String> mockList;

    @Test
    public void testing() {
        when(mockList.add("one")).thenReturn(true); // this won't get called
        System.out.println("Hello world!");
    }
}

This is succesfull in Mockito versions:

This is throwing exception in following Mockito versions:

This exception can be silenced with @RunWith(MockitoJUnitRunner.Silent.class) instead.

@\Rule MockitoRule

public class TestingTest {

    @Rule
    public MockitoRule rule = MockitoJUnit.rule();

    @Mock
    private List<String> mockList;

    @Test
    public void testing() {
        when(mockList.add("one")).thenReturn(true); // this won't get called
        System.out.println("Hello world!");
    }
}

This is succesfull in Mockito versions:

Mockito.mock

public class TestingTest {

    private List<String> mockList = Mockito.mock(List.class);

    @Test
    public void testing() {
        when(mockList.add("one")).thenReturn(true); // this won't get called
        System.out.println("Hello world!");
    }
}

This is succesfull in Mockito versions:

MockitoAnnotations.initMocks

public class TestingTest {

    @Mock
    private List<String> mockList;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testing() {
        when(mockList.add("one")).thenReturn(true); // this won't get called
        System.out.println("Hello world!");
    }
}

This is succesfull in Mockito versions:

timtebeek commented 1 month ago

Oh wow, no wonder the docs are confusing. 😅 Thanks a lot for the detailed analysis and report!

Wondering what to do here, as we do have recipes to migrate most of those onto JUnit 5, which would then start to fail on 2.17+ if we do not introduce @MockitoSettings(strictness = Strictness.WARN). But that would also mean you could potentially migrate a 4.x Mockito.mock or MockitoAnnotations.initMocks class to the MockitoExtension and have to introduce that @MockitoSettings there to keep the old behaviour.

Sounds like potentially our ScanningRecipe needs to keep track of what each test class was using (whenever we come from a pre-5 version), and add the annotation to the matching tests, if we want to be minimal in when we introduce that annotation. Would you agree @Jenson3210 ?

Jenson3210 commented 1 month ago

I was at the same time writing a summary here. 😄 I think the actual bug is inside the MockitoJUnitToMockitoExtension recipe. This one is responsible for migrating MockitoRule to Extension.

For Mockito.mock and MockitoAnnotations.initMocks there is no problem as their is no exception.

For the MockitoRule, the responsability is in the MockitoJUnitToMockitoExtension recipe. This one does not set MockitoSettings if that would be necessary --> Is this not the real bug then?

For the RunWith(MockitoJUnitRunner.class), we have RunnerToExtension, for the silenced equivalent we have MockitoJUnitRunnerSilentToExtension

I feel this means RetainStrictnessWarn does a check it maybe did not really needed to begin with. But as it is there, and this recipe is used in a declarative recipe also already related to specific versions, maybe it is better to no touch that one but to fix the rule -> settings migration so that it adds the annotations if required.

What do you think @timtebeek

timtebeek commented 1 month ago

I agree based on the above that perhaps indeed adding the settings should be done at the same time as migrating from rules to extension. That seems to be the point where the behavior and defaults are changed, in a way that can not be reliably determined after those recipes have run based on the version number alone. I'd be open to even remove the RetainStrictnessWarn recipe; better to have one recipe reliably make those changes.

Jenson3210 commented 1 month ago

I'll get started on some unit tests. Basically after a round of testing there, If both silent and strictness are mentioned, strictness takes precedence. If any of the two is set, annotation should be added.

i think that mapping is the best

Jenson3210 commented 1 month ago

@timtebeek, The code is ready and test are added.

However, I am getting this issue with my 4 added tests. My class is all the time being prefixed with a space and I have no clue. Would you happen to know?

Screenshot 2024-10-30 at 00 46 52
Jenson3210 commented 1 month ago

Looks like this bug is also present in current recipe already strange the tests run... Screenshot 2024-10-30 at 08 53 55