diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d48f39..44414d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ ## [Unreleased] ## [0.5.0] +### Added +- Strictness configuration is now supported by the conversion between `@Mock` and `Mockito.mock(Class, MockSettings)` in both directions. + ### Changed - Removed support for IJ2021.1. From now on 2021.2 is the earliest version supported. diff --git a/README.md b/README.md index ee6e54c..6e7e427 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,8 @@ You can find the list of those inspections in the [Mockitools/SonarLint rules](d ## Why Mockitools is tasty -If [*"Mockito ... tastes really good"* and *"doesn’t give you hangover"*](https://site.mockito.org/#why), this IDE plugin will only make it better. You can find out why, in the dedicated documentation below. +If [*"Mockito ... tastes really good"* and *"doesn’t give you hangover"*](https://site.mockito.org/#why), +this IDE plugin will only make it better. You can find out why, in the dedicated documentation below. - [Mock creation](docs/mock_creation.md) - [Stubbing](docs/stubbing.md) diff --git a/docs/mock_creation.md b/docs/mock_creation.md index e006fd5..1b9eb14 100644 --- a/docs/mock_creation.md +++ b/docs/mock_creation.md @@ -318,6 +318,9 @@ to: @Mock Clazz clazz; from: mock(Clazz.class, Mockito.withSettings().extraInterfaces(List.class)) to: @Mock(extraInterfaces = List.class) Clazz clazz; + +from: mock(Clazz.class, Mockito.withSettings().strictness(Strictness.WARN)) +to: @Mock(strictness = Mock.Strictness.WARN) Clazz clazz; ``` Furthermore, the type that is being mocked should be mockable either by Mockito's rules or not being annotated with `@DoNotMock`. diff --git a/lib/mockito-core-4.2.0.jar b/lib/mockito-core-4.6.1.jar similarity index 67% rename from lib/mockito-core-4.2.0.jar rename to lib/mockito-core-4.6.1.jar index 2fedb0f..e694e19 100644 Binary files a/lib/mockito-core-4.2.0.jar and b/lib/mockito-core-4.6.1.jar differ diff --git a/src/main/java/com/picimako/mockitools/MockitoolsPsiUtil.java b/src/main/java/com/picimako/mockitools/MockitoolsPsiUtil.java index 8059bfe..1f43a6d 100644 --- a/src/main/java/com/picimako/mockitools/MockitoolsPsiUtil.java +++ b/src/main/java/com/picimako/mockitools/MockitoolsPsiUtil.java @@ -66,7 +66,7 @@ public final class MockitoolsPsiUtil { */ private static final Set NON_MOCKABLE_TYPES = Set.of(CommonClassNames.JAVA_LANG_CLASS, CommonClassNames.JAVA_LANG_STRING); - private static final CallMatcher MOCKITO_MOCK = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, MOCK); + public static final CallMatcher.Simple MOCKITO_MOCK = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, MOCK); private static final CallMatcher MOCKITO_SPY = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, SPY).parameterCount(1); private static final CallMatcher BDDMOCKITO_GIVEN = CallMatcher.staticCall(ORG_MOCKITO_BDDMOCKITO, GIVEN).parameterCount(1); private static final CallMatcher BDDMOCKITO_WILL_X = diff --git a/src/main/java/com/picimako/mockitools/intention/ConvertMockCallToFieldIntention.java b/src/main/java/com/picimako/mockitools/intention/ConvertMockCallToFieldIntention.java index 68b51bf..45c6b1d 100644 --- a/src/main/java/com/picimako/mockitools/intention/ConvertMockCallToFieldIntention.java +++ b/src/main/java/com/picimako/mockitools/intention/ConvertMockCallToFieldIntention.java @@ -7,7 +7,6 @@ import static com.picimako.mockitools.MockitoQualifiedNames.DEFAULT_ANSWER; import static com.picimako.mockitools.MockitoQualifiedNames.EXTRA_INTERFACES; import static com.picimako.mockitools.MockitoQualifiedNames.LENIENT; -import static com.picimako.mockitools.MockitoQualifiedNames.MOCK; import static com.picimako.mockitools.MockitoQualifiedNames.NAME; import static com.picimako.mockitools.MockitoQualifiedNames.ORG_MOCKITO_ANSWER; import static com.picimako.mockitools.MockitoQualifiedNames.ORG_MOCKITO_ANSWERS; @@ -17,6 +16,7 @@ import static com.picimako.mockitools.MockitoQualifiedNames.ORG_MOCKITO_MOCK_SETTINGS; import static com.picimako.mockitools.MockitoQualifiedNames.SERIALIZABLE; import static com.picimako.mockitools.MockitoQualifiedNames.STUB_ONLY; +import static com.picimako.mockitools.MockitoolsPsiUtil.MOCKITO_MOCK; import static com.picimako.mockitools.MockitoolsPsiUtil.isMockableTypeInAnyWay; import static com.picimako.mockitools.PsiMethodUtil.collectCallsInChainFromLast; import static com.picimako.mockitools.PsiMethodUtil.get2ndArgument; @@ -31,10 +31,6 @@ import static com.siyeh.ig.psiutils.MethodCallUtils.getMethodName; import static java.util.stream.Collectors.joining; -import java.util.Arrays; -import java.util.Set; -import java.util.function.Supplier; - import com.intellij.ide.highlighter.JavaFileType; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; @@ -50,10 +46,14 @@ import com.intellij.psi.PsiMethodCallExpression; import com.intellij.psi.PsiReferenceExpression; import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.picimako.mockitools.MockitoQualifiedNames; +import com.picimako.mockitools.PsiClassUtil; import com.siyeh.ig.callMatcher.CallMatcher; import org.jetbrains.annotations.NotNull; -import com.picimako.mockitools.MockitoQualifiedNames; +import java.util.Arrays; +import java.util.Set; +import java.util.function.Supplier; /** * Converts {@code Mockito.mock()} calls to {@code @Mock} annotated fields. @@ -64,6 +64,7 @@ * mock(Clazz.class, "some name") -> @Mock(name = "some name") Clazz clazz; * mock(Clazz.class, Answers.RETURNS_SMART_NULLS) -> @Mock(answer = Answers.RETURNS_SMART_NULLS) Clazz clazz; * mock(Clazz.class, Mockito.withSettings().lenient()) -> @Mock(lenient = true) Clazz clazz; + * mock(Clazz.class, Mockito.withSettings().strictness(Strictness.WARN)) -> @Mock(strictness = Mock.Strictness.WARN) Clazz clazz; * * Clazz localVar = mock(Clazz.class) -> @Mock Clazz localVar; * Clazz<typeargs> localVar = mock(Clazz.class) -> @Mock Clazz<typeargs> localVar; @@ -89,17 +90,17 @@ */ public class ConvertMockCallToFieldIntention extends ConvertCallToFieldIntentionBase { private static final String JAVA_LANG_CLASS = "java.lang.Class"; - private static final CallMatcher MOCKITO_MOCK = staticCall(ORG_MOCKITO_MOCKITO, MOCK).parameterTypes(JAVA_LANG_CLASS); - private static final CallMatcher MOCK_WITH_NAME = staticCall(ORG_MOCKITO_MOCKITO, MOCK).parameterTypes(JAVA_LANG_CLASS, CommonClassNames.JAVA_LANG_STRING); - private static final CallMatcher MOCK_WITH_ANSWER = staticCall(ORG_MOCKITO_MOCKITO, MOCK).parameterTypes(JAVA_LANG_CLASS, ORG_MOCKITO_ANSWER); - private static final CallMatcher MOCK_WITH_SETTINGS = staticCall(ORG_MOCKITO_MOCKITO, MOCK).parameterTypes(JAVA_LANG_CLASS, ORG_MOCKITO_MOCK_SETTINGS); + private static final CallMatcher MOCK = MOCKITO_MOCK.parameterTypes(JAVA_LANG_CLASS); + private static final CallMatcher MOCK_WITH_NAME = MOCKITO_MOCK.parameterTypes(JAVA_LANG_CLASS, CommonClassNames.JAVA_LANG_STRING); + private static final CallMatcher MOCK_WITH_ANSWER = MOCKITO_MOCK.parameterTypes(JAVA_LANG_CLASS, ORG_MOCKITO_ANSWER); + private static final CallMatcher MOCK_WITH_SETTINGS = MOCKITO_MOCK.parameterTypes(JAVA_LANG_CLASS, ORG_MOCKITO_MOCK_SETTINGS); private static final CallMatcher MOCKITO_WITH_SETTINGS = staticCall(ORG_MOCKITO_MOCKITO, "withSettings"); private static final CallMatcher MOCK_SETTINGS_SERIALIZABLE_WITH_MODE = instanceCall(ORG_MOCKITO_MOCK_SETTINGS, SERIALIZABLE).parameterTypes(ORG_MOCKITO_MOCK_SERIALIZABLE_MODE); - private static final Set SUPPORTED_MOCK_SETTINGS_METHODS = Set.of(DEFAULT_ANSWER, STUB_ONLY, NAME, EXTRA_INTERFACES, LENIENT); + private static final Set SUPPORTED_MOCK_SETTINGS_METHODS = Set.of(DEFAULT_ANSWER, STUB_ONLY, NAME, EXTRA_INTERFACES, LENIENT, "strictness"); public ConvertMockCallToFieldIntention() { - super(MOCK, "@Mock"); + super(MockitoQualifiedNames.MOCK, "@Mock"); } //Availability @@ -120,13 +121,16 @@ public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file final PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); if (isIdentifierOfMethodCall(element)) { var methodCall = (PsiMethodCallExpression) element.getParent().getParent(); - if (!MOCK.equals(getMethodName(methodCall))) return false; + if (!MockitoQualifiedNames.MOCK.equals(getMethodName(methodCall))) return false; var mockTypeArg = getFirstArgument(methodCall); - if (!(mockTypeArg instanceof PsiClassObjectAccessExpression) || !isMockableTypeInAnyWay(getOperandType(mockTypeArg))) return false; - if (MOCKITO_MOCK.matches(methodCall)) return hasOneArgument(methodCall); - if (MOCK_WITH_NAME.matches(methodCall) || MOCK_WITH_ANSWER.matches(methodCall)) return hasTwoArguments(methodCall); - if (MOCK_WITH_SETTINGS.matches(methodCall)) return hasTwoArguments(methodCall) && isSettingsSupportedByMockAnnotation(get2ndArgument(methodCall)); + if (!(mockTypeArg instanceof PsiClassObjectAccessExpression) || !isMockableTypeInAnyWay(getOperandType(mockTypeArg))) + return false; + if (MOCK.matches(methodCall)) return hasOneArgument(methodCall); + if (MOCK_WITH_NAME.matches(methodCall) || MOCK_WITH_ANSWER.matches(methodCall)) + return hasTwoArguments(methodCall); + if (MOCK_WITH_SETTINGS.matches(methodCall)) + return hasTwoArguments(methodCall) && isSettingsSupportedByMockAnnotation(get2ndArgument(methodCall)); } return false; } @@ -198,10 +202,12 @@ private void configureFromMockSettings(MockSettingsBasedAnnotationConfigurer con for (int i = calls.size() - 2; i >= 0; i--) { var call = calls.get(i); String methodName = getMethodName(call); - if (STUB_ONLY.equals(methodName) || LENIENT.equals(methodName) || SERIALIZABLE.equals(methodName)) configurer.configureBooleanAttribute(methodName); + if (STUB_ONLY.equals(methodName) || LENIENT.equals(methodName) || SERIALIZABLE.equals(methodName)) + configurer.configureBooleanAttribute(methodName); else if (NAME.equals(methodName)) configurer.configureName(call); else if (DEFAULT_ANSWER.equals(methodName)) configurer.configureAnswerFromCall(call); else if (EXTRA_INTERFACES.equals(methodName)) configurer.configureExtraInterfaces(call); + else if ("strictness".equals(methodName)) configurer.configureStrictness(call); } } @@ -209,13 +215,14 @@ private void configureFromMockSettings(MockSettingsBasedAnnotationConfigurer con * Returns whether the argument Answer expression is a reference to {@code org.mockito.Answers.RETURNS_DEFAULTS}. */ public static boolean isDefaultAnswer(PsiExpression answer) { - if (answer instanceof PsiReferenceExpression) { - PsiElement resolved = ((PsiReferenceExpression) answer).resolve(); - return resolved instanceof PsiEnumConstant - && ORG_MOCKITO_ANSWERS.equals(((PsiEnumConstant) resolved).getContainingClass().getQualifiedName()) - && "RETURNS_DEFAULTS".equals(((PsiEnumConstant) resolved).getName()); - } - return false; + return answer instanceof PsiReferenceExpression + && isEnumConstant(((PsiReferenceExpression) answer).resolve(), ORG_MOCKITO_ANSWERS, "RETURNS_DEFAULTS"); + } + + private static boolean isEnumConstant(PsiElement constant, String enumClassName, String enumConstantName) { + return constant instanceof PsiEnumConstant + && enumClassName.equals(((PsiEnumConstant) constant).getContainingClass().getQualifiedName()) + && enumConstantName.equals(((PsiEnumConstant) constant).getName()); } /** @@ -262,6 +269,20 @@ private void configureExtraInterfaces(PsiMethodCallExpression call) { } } + private void configureStrictness(PsiMethodCallExpression call) { + var strictness = getFirstArgument(call); + //null value passed into MockSettings.strictness() is not handled since it is invalid anyway. + if (strictness instanceof PsiReferenceExpression) { + PsiElement resolved = ((PsiReferenceExpression) strictness).resolve(); + if (resolved instanceof PsiEnumConstant) { + PsiEnumConstant constant = (PsiEnumConstant) resolved; + String strictnessName = constant.getName(); + PsiClassUtil.importClass("org.mockito.Mock.Strictness", mockAnnotation); + mockAnnotation.setDeclaredAttributeValue("strictness", attributeValue("Mock.Strictness." + strictnessName)); + } + } + } + @NotNull private PsiExpression attributeValue(String text) { return JavaPsiFacade.getElementFactory(project).createExpressionFromText(text, mockAnnotation); diff --git a/src/test/java/com/picimako/mockitools/ThirdPartyLibraryLoader.java b/src/test/java/com/picimako/mockitools/ThirdPartyLibraryLoader.java index c689d24..c78056b 100644 --- a/src/test/java/com/picimako/mockitools/ThirdPartyLibraryLoader.java +++ b/src/test/java/com/picimako/mockitools/ThirdPartyLibraryLoader.java @@ -23,7 +23,7 @@ public static void loadMockito3(@NotNull Disposable projectDisposable, @NotNull } public static void loadMockito4Latest(@NotNull Disposable projectDisposable, @NotNull Module module) { - loadLibrary(projectDisposable, module, "Mockito 4 Library", "mockito-core-4.2.0.jar"); + loadLibrary(projectDisposable, module, "Mockito 4 Library", "mockito-core-4.6.1.jar"); } public static void loadJUnit4(@NotNull Disposable projectDisposable, @NotNull Module module) { diff --git a/src/test/java/com/picimako/mockitools/intention/ConvertMockCallToFieldIntentionTest.java b/src/test/java/com/picimako/mockitools/intention/ConvertMockCallToFieldIntentionTest.java index 9fb0225..767b108 100644 --- a/src/test/java/com/picimako/mockitools/intention/ConvertMockCallToFieldIntentionTest.java +++ b/src/test/java/com/picimako/mockitools/intention/ConvertMockCallToFieldIntentionTest.java @@ -439,6 +439,32 @@ public void testConvertsMockitoMockWithSettingsNameFromVariable() { "}"); } + public void testConvertsMockitoMockWithSettingsStrictness() { + checkIntentionRun( + "import org.mockito.Mockito;\n" + + "import org.mockito.quality.Strictness;\n" + + "\n" + + "public class ConversionTest {\n" + + " public void testMethod() {\n" + + " aMethod(Mockito.mock(Object.class, Mockito.withSettings().strictness(Strictness.WARN)));\n" + + " }\n" + + " public void aMethod(Object object) { }\n" + + "}", + "import org.mockito.Mock;\n" + + "import org.mockito.Mockito;\n" + + "import org.mockito.quality.Strictness;\n" + + "\n" + + "public class ConversionTest {\n" + + " @Mock(strictness = Mock.Strictness.WARN)\n" + + " Object object;\n" + + "\n" + + " public void testMethod() {\n" + + " aMethod(object);\n" + + " }\n" + + " public void aMethod(Object object) { }\n" + + "}"); + } + public void testConvertsMockitoMockWithSettingsDefaultAnswer() { checkIntentionRun( "import org.mockito.Mockito;\n" +