From 85c733394749293bf7ef8c3dd080520b4b6fcfca Mon Sep 17 00:00:00 2001 From: arnor2000 Date: Thu, 5 Mar 2020 19:46:23 +0100 Subject: [PATCH] Fixes #1516 and #1631 Allows Spies with @InjectMocks to be injected into other @InjectMocks --- .../configuration/DefaultInjectionEngine.java | 31 ++ .../InjectingAnnotationEngine.java | 16 +- .../configuration/SpyAnnotationEngine.java | 29 +- .../injection/ConstructorInjection.java | 55 +-- .../LenientPropertyAndSetterInjection.java | 27 ++ .../injection/MockInjection.java | 10 + .../injection/PropertyAndSetterInjection.java | 16 +- .../injection/StrictConstructorInjection.java | 26 ++ .../filter/TerminalMockCandidateFilter.java | 3 +- .../injection/scanner/MockScanner.java | 21 +- .../mockito/internal/exceptions/Reporter.java | 20 +- .../util/reflection/ConstructorResolver.java | 298 +++++++++++++++ .../reflection/FieldInitializationReport.java | 28 +- .../util/reflection/FieldInitializer.java | 226 ++--------- .../injection/ConstructorInjectionTest.java | 60 ++- ...LenientPropertyAndSetterInjectionTest.java | 48 +++ .../PropertyAndSetterInjectionTest.java | 187 ++++++++++ .../injection/SimpleArgumentResolverTest.java | 61 --- .../StrictConstructorInjectionTest.java | 37 ++ .../BiggestConstructorResolverTest.java | 353 ++++++++++++++++++ .../util/reflection/FieldInitializerTest.java | 140 +++++-- .../LenientNoArgsConstructorResolverTest.java | 46 +++ .../NoArgsConstructorResolverTest.java | 143 +++++++ ...ameterizedConstructorInstantiatorTest.java | 150 -------- .../StrictBiggestConstructorResolverTest.java | 69 ++++ ...tingAnnotationEngineByConstructorTest.java | 78 ++++ ...jectingAnnotationEngineByPropertyTest.java | 71 ++++ ...ineCircularInjectionByConstructorTest.java | 61 +++ ...EngineCircularInjectionByPropertyTest.java | 71 ++++ 29 files changed, 1866 insertions(+), 515 deletions(-) create mode 100644 src/main/java/org/mockito/internal/configuration/injection/LenientPropertyAndSetterInjection.java create mode 100644 src/main/java/org/mockito/internal/configuration/injection/StrictConstructorInjection.java create mode 100644 src/main/java/org/mockito/internal/util/reflection/ConstructorResolver.java create mode 100644 src/test/java/org/mockito/internal/configuration/injection/LenientPropertyAndSetterInjectionTest.java create mode 100644 src/test/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjectionTest.java delete mode 100644 src/test/java/org/mockito/internal/configuration/injection/SimpleArgumentResolverTest.java create mode 100644 src/test/java/org/mockito/internal/configuration/injection/StrictConstructorInjectionTest.java create mode 100644 src/test/java/org/mockito/internal/util/reflection/BiggestConstructorResolverTest.java create mode 100644 src/test/java/org/mockito/internal/util/reflection/LenientNoArgsConstructorResolverTest.java create mode 100644 src/test/java/org/mockito/internal/util/reflection/NoArgsConstructorResolverTest.java delete mode 100644 src/test/java/org/mockito/internal/util/reflection/ParameterizedConstructorInstantiatorTest.java create mode 100644 src/test/java/org/mockito/internal/util/reflection/StrictBiggestConstructorResolverTest.java create mode 100644 src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineByConstructorTest.java create mode 100644 src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineByPropertyTest.java create mode 100644 src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineCircularInjectionByConstructorTest.java create mode 100644 src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineCircularInjectionByPropertyTest.java diff --git a/src/main/java/org/mockito/internal/configuration/DefaultInjectionEngine.java b/src/main/java/org/mockito/internal/configuration/DefaultInjectionEngine.java index e5e98a1a20..ad6c1d59f4 100644 --- a/src/main/java/org/mockito/internal/configuration/DefaultInjectionEngine.java +++ b/src/main/java/org/mockito/internal/configuration/DefaultInjectionEngine.java @@ -16,6 +16,37 @@ */ public class DefaultInjectionEngine { + /** + * Proceeds to ongoing mocks injection on fields with: + * + * + * @param needingInjection fields needing injection + * @param mocks mocks available for injection + * @param testClassInstance instance of the test + */ + public void injectOngoingMocksOnFields(Set needingInjection, Set mocks, Object testClassInstance) { + MockInjection.onFields(needingInjection, testClassInstance) + .withMocks(mocks) + .tryStrictConstructorInjection() + .tryLenientPropertyOrFieldInjection() + .handleSpyAnnotation() + .apply(); + } + + /** + * Proceeds to terminal mocks injection on fields with: + *
    + *
  • lenient constructor injection strategy to initialize fields even with null arguments + *
  • strict field/property injection strategy to fail on fields without no-args constructor + *
+ * + * @param needingInjection fields needing injection + * @param mocks mocks available for injection + * @param testClassInstance instance of the test + */ public void injectMocksOnFields(Set needingInjection, Set mocks, Object testClassInstance) { MockInjection.onFields(needingInjection, testClassInstance) .withMocks(mocks) diff --git a/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java b/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java index 1fba127ef0..134d3e4fb7 100644 --- a/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java +++ b/src/main/java/org/mockito/internal/configuration/InjectingAnnotationEngine.java @@ -40,15 +40,7 @@ public class InjectingAnnotationEngine implements AnnotationEngine, org.mockito. */ public void process(Class clazz, Object testInstance) { processIndependentAnnotations(testInstance.getClass(), testInstance); - processInjectMocks(testInstance.getClass(), testInstance); - } - - private void processInjectMocks(final Class clazz, final Object testInstance) { - Class classContext = clazz; - while (classContext != Object.class) { - injectMocks(testInstance); - classContext = classContext.getSuperclass(); - } + injectMocks(testInstance); } private void processIndependentAnnotations(final Class clazz, final Object testInstance) { @@ -85,6 +77,12 @@ public void injectMocks(final Object testClassInstance) { clazz = clazz.getSuperclass(); } + Set previousMocks; + do { + previousMocks = mocks; + new DefaultInjectionEngine().injectOngoingMocksOnFields(mockDependentFields, mocks, testClassInstance); + mocks = new MockScanner(testClassInstance, testClassInstance.getClass()).scanHierarchy(); + } while (!previousMocks.equals(mocks)); new DefaultInjectionEngine().injectMocksOnFields(mockDependentFields, mocks, testClassInstance); } diff --git a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java index 1a9ce04155..396794c545 100644 --- a/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java +++ b/src/main/java/org/mockito/internal/configuration/SpyAnnotationEngine.java @@ -37,8 +37,9 @@ *

*

*

- * If the field is also annotated with the compatible @InjectMocks then the field will be ignored, - * The injection engine will handle this specific case. + * If the field is also annotated with the compatible @InjectMocks and has + * parameterized constructor then the field will be ignored, the injection engine will handle this + * specific case. *

*

*

This engine will fail, if the field is also annotated with incompatible Mockito annotations. @@ -50,7 +51,7 @@ public class SpyAnnotationEngine implements AnnotationEngine, org.mockito.config public void process(Class context, Object testInstance) { Field[] fields = context.getDeclaredFields(); for (Field field : fields) { - if (field.isAnnotationPresent(Spy.class) && !field.isAnnotationPresent(InjectMocks.class)) { + if (shouldProcess(field)) { assertNoIncompatibleAnnotations(Spy.class, field, Mock.class, Captor.class); field.setAccessible(true); Object instance; @@ -72,6 +73,19 @@ public void process(Class context, Object testInstance) { } } + private boolean shouldProcess(Field field) { + if (!field.isAnnotationPresent(Spy.class)) { + return false; + } + if (!field.isAnnotationPresent(InjectMocks.class)) { + return true; + } + if (field.getType().isInterface()) { + return false; + } + return !hasParameterizedConstructor(field.getType()); + } + private static Object spyInstance(Field field, Object instance) { return Mockito.mock(instance.getClass(), withSettings().spiedInstance(instance) @@ -116,6 +130,15 @@ private static Object spyNewInstance(Object testInstance, Field field) } } + private static boolean hasParameterizedConstructor(Class type) { + for (Constructor constructor : type.getDeclaredConstructors()) { + if (constructor.getParameterTypes().length > 0) { + return true; + } + } + return false; + } + private static Constructor noArgConstructorOf(Class type) { Constructor constructor; try { diff --git a/src/main/java/org/mockito/internal/configuration/injection/ConstructorInjection.java b/src/main/java/org/mockito/internal/configuration/injection/ConstructorInjection.java index 380fb93da1..2e8ab0665b 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/ConstructorInjection.java +++ b/src/main/java/org/mockito/internal/configuration/injection/ConstructorInjection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 Mockito contributors + * Copyright (c) 2020 Mockito contributors * This program is made available under the terms of the MIT License. */ package org.mockito.internal.configuration.injection; @@ -8,45 +8,32 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; import java.util.Set; import org.mockito.exceptions.base.MockitoException; +import org.mockito.internal.util.reflection.ConstructorResolver; +import org.mockito.internal.util.reflection.ConstructorResolver.BiggestConstructorResolver; import org.mockito.internal.util.reflection.FieldInitializationReport; import org.mockito.internal.util.reflection.FieldInitializer; -import org.mockito.internal.util.reflection.FieldInitializer.ConstructorArgumentResolver; /** * Injection strategy based on constructor. * *

* The strategy will search for the constructor with most parameters - * and try to resolve mocks by type. - *

- * - *
- * TODO on missing mock type, shall it abandon or create "noname" mocks. - * TODO and what if the arg type is not mockable. - *
- * - *

- * For now the algorithm tries to create anonymous mocks if an argument type is missing. - * If not possible the algorithm abandon resolution. + * and try to resolve mocks by type, or null if there is no mocks matching a parameter. *

*/ public class ConstructorInjection extends MockInjectionStrategy { - public ConstructorInjection() { } - public boolean processInjection(Field field, Object fieldOwner, Set mockCandidates) { try { - SimpleArgumentResolver simpleArgumentResolver = new SimpleArgumentResolver(mockCandidates); - FieldInitializationReport report = new FieldInitializer(fieldOwner, field, simpleArgumentResolver).initialize(); + ConstructorResolver constructorResolver = createConstructorResolver(field.getType(), mockCandidates); + FieldInitializationReport report = new FieldInitializer(fieldOwner, field, constructorResolver).initialize(); - return report.fieldWasInitializedUsingContructorArgs(); + return report.fieldWasInitialized(); } catch (MockitoException e) { - if(e.getCause() instanceof InvocationTargetException) { + if (e.getCause() instanceof InvocationTargetException) { Throwable realCause = e.getCause().getCause(); throw fieldInitialisationThrewException(field, realCause); } @@ -56,30 +43,8 @@ public boolean processInjection(Field field, Object fieldOwner, Set mock } - /** - * Returns mocks that match the argument type, if not possible assigns null. - */ - static class SimpleArgumentResolver implements ConstructorArgumentResolver { - final Set objects; - - public SimpleArgumentResolver(Set objects) { - this.objects = objects; - } - - public Object[] resolveTypeInstances(Class... argTypes) { - List argumentInstances = new ArrayList(argTypes.length); - for (Class argType : argTypes) { - argumentInstances.add(objectThatIsAssignableFrom(argType)); - } - return argumentInstances.toArray(); - } - - private Object objectThatIsAssignableFrom(Class argType) { - for (Object object : objects) { - if(argType.isAssignableFrom(object.getClass())) return object; - } - return null; - } + protected ConstructorResolver createConstructorResolver(Class fieldType, Set mockCandidates) { + return new BiggestConstructorResolver(fieldType, mockCandidates); } } diff --git a/src/main/java/org/mockito/internal/configuration/injection/LenientPropertyAndSetterInjection.java b/src/main/java/org/mockito/internal/configuration/injection/LenientPropertyAndSetterInjection.java new file mode 100644 index 0000000000..84a44b7605 --- /dev/null +++ b/src/main/java/org/mockito/internal/configuration/injection/LenientPropertyAndSetterInjection.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.configuration.injection; + +import org.mockito.internal.util.reflection.ConstructorResolver; +import org.mockito.internal.util.reflection.ConstructorResolver.LenientNoArgsConstructorResolver; + +/** + * Inject mocks using setters then fields, if no setters available, see + * {@link PropertyAndSetterInjection parent class} for more information on algorithm. + *

+ * The strategy to instantiate field (if needed) is to try to find no-args constructor on field type + * and skip the field otherwise. + *

+ * + * @see org.mockito.internal.configuration.injection.PropertyAndSetterInjection + */ +public class LenientPropertyAndSetterInjection extends PropertyAndSetterInjection { + + @Override + protected ConstructorResolver createConstructorResolver(Class fieldType) { + return new LenientNoArgsConstructorResolver(fieldType); + } + +} diff --git a/src/main/java/org/mockito/internal/configuration/injection/MockInjection.java b/src/main/java/org/mockito/internal/configuration/injection/MockInjection.java index 550599625e..6b3eac62b4 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/MockInjection.java +++ b/src/main/java/org/mockito/internal/configuration/injection/MockInjection.java @@ -76,11 +76,21 @@ public OngoingMockInjection tryConstructorInjection() { return this; } + public OngoingMockInjection tryStrictConstructorInjection() { + injectionStrategies.thenTry(new StrictConstructorInjection()); + return this; + } + public OngoingMockInjection tryPropertyOrFieldInjection() { injectionStrategies.thenTry(new PropertyAndSetterInjection()); return this; } + public OngoingMockInjection tryLenientPropertyOrFieldInjection() { + injectionStrategies.thenTry(new LenientPropertyAndSetterInjection()); + return this; + } + public OngoingMockInjection handleSpyAnnotation() { postInjectionStrategies.thenTry(new SpyOnInjectedFieldsHandler()); return this; diff --git a/src/main/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjection.java b/src/main/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjection.java index 38f5760226..1f7cc92df4 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjection.java +++ b/src/main/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjection.java @@ -23,6 +23,8 @@ import org.mockito.internal.configuration.injection.filter.TerminalMockCandidateFilter; import org.mockito.internal.configuration.injection.filter.TypeBasedCandidateFilter; import org.mockito.internal.util.collections.ListUtil; +import org.mockito.internal.util.reflection.ConstructorResolver; +import org.mockito.internal.util.reflection.ConstructorResolver.NoArgsConstructorResolver; import org.mockito.internal.util.reflection.FieldInitializationReport; import org.mockito.internal.util.reflection.FieldInitializer; @@ -56,8 +58,8 @@ *

* *

- * Note: If the field needing injection is not initialized, the strategy tries - * to create one using a no-arg constructor of the field type. + * Note: If the field needing injection is not initialized, the strategy tries to create one + * using a no-arg constructor of the field type or fails with an explicit message. *

*/ public class PropertyAndSetterInjection extends MockInjectionStrategy { @@ -77,6 +79,10 @@ public boolean isOut(Field object) { public boolean processInjection(Field injectMocksField, Object injectMocksFieldOwner, Set mockCandidates) { FieldInitializationReport report = initializeInjectMocksField(injectMocksField, injectMocksFieldOwner); + if (!report.fieldIsInitialized()) { + return false; + } + // for each field in the class hierarchy boolean injectionOccurred = false; Class fieldClass = report.fieldClass(); @@ -90,7 +96,8 @@ public boolean processInjection(Field injectMocksField, Object injectMocksFieldO private FieldInitializationReport initializeInjectMocksField(Field field, Object fieldOwner) { try { - return new FieldInitializer(fieldOwner, field).initialize(); + final ConstructorResolver constructorResolver = createConstructorResolver(field.getType()); + return new FieldInitializer(fieldOwner, field, constructorResolver).initialize(); } catch (MockitoException e) { if(e.getCause() instanceof InvocationTargetException) { Throwable realCause = e.getCause().getCause(); @@ -100,6 +107,9 @@ private FieldInitializationReport initializeInjectMocksField(Field field, Object } } + protected ConstructorResolver createConstructorResolver(Class fieldType) { + return new NoArgsConstructorResolver(fieldType); + } private boolean injectMockCandidates(Class awaitingInjectionClazz, Object injectee, Set mocks) { boolean injectionOccurred; diff --git a/src/main/java/org/mockito/internal/configuration/injection/StrictConstructorInjection.java b/src/main/java/org/mockito/internal/configuration/injection/StrictConstructorInjection.java new file mode 100644 index 0000000000..c2dc4a61b3 --- /dev/null +++ b/src/main/java/org/mockito/internal/configuration/injection/StrictConstructorInjection.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.configuration.injection; + +import java.util.Set; + +import org.mockito.internal.util.reflection.ConstructorResolver; +import org.mockito.internal.util.reflection.ConstructorResolver.StrictBiggestConstructorResolver; + +/** + * Injection strategy based on constructor. + *

+ * The strategy will search for the constructor with most parameters and try to resolve mocks by + * type or skip the field if there is no mocks matching a parameter. + *

+ */ +public class StrictConstructorInjection extends ConstructorInjection { + + @Override + protected ConstructorResolver createConstructorResolver(Class fieldType, Set mockCandidates) { + return new StrictBiggestConstructorResolver(fieldType, mockCandidates); + } + +} diff --git a/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java b/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java index 4644e218aa..2c0fdb393f 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java +++ b/src/main/java/org/mockito/internal/configuration/injection/filter/TerminalMockCandidateFilter.java @@ -36,7 +36,8 @@ public Object thenInject() { setField(injectee, candidateFieldToBeInjected,matchingMock); } } catch (RuntimeException e) { - throw cannotInjectDependency(candidateFieldToBeInjected, matchingMock, e); + final Throwable details = e.getCause() == null ? e : e.getCause(); + throw cannotInjectDependency(candidateFieldToBeInjected, matchingMock, details); } return matchingMock; } diff --git a/src/main/java/org/mockito/internal/configuration/injection/scanner/MockScanner.java b/src/main/java/org/mockito/internal/configuration/injection/scanner/MockScanner.java index b7da87246b..982e447424 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/scanner/MockScanner.java +++ b/src/main/java/org/mockito/internal/configuration/injection/scanner/MockScanner.java @@ -42,7 +42,22 @@ public MockScanner(Object instance, Class clazz) { * @param mocks Set of mocks */ public void addPreparedMocks(Set mocks) { - mocks.addAll(scan()); + scan(clazz, mocks); + } + + /** + * Scan and prepare mocks for the whole hierarchy of given testClassInstance. + * + * @return A prepared set of mock + */ + public Set scanHierarchy() { + final Set mocks = newMockSafeHashSet(); + Class currentClass = clazz; + while (currentClass != Object.class) { + scan(currentClass, mocks); + currentClass = currentClass.getSuperclass(); + } + return mocks; } /** @@ -50,8 +65,7 @@ public void addPreparedMocks(Set mocks) { * * @return A prepared set of mock */ - private Set scan() { - Set mocks = newMockSafeHashSet(); + private void scan(Class clazz, Set mocks) { for (Field field : clazz.getDeclaredFields()) { // mock or spies only FieldReader fieldReader = new FieldReader(instance, field); @@ -61,7 +75,6 @@ private Set scan() { mocks.add(mockInstance); } } - return mocks; } private Object preparedMock(Object instance, Field field) { diff --git a/src/main/java/org/mockito/internal/exceptions/Reporter.java b/src/main/java/org/mockito/internal/exceptions/Reporter.java index f661b81e5c..3f69e0f44d 100644 --- a/src/main/java/org/mockito/internal/exceptions/Reporter.java +++ b/src/main/java/org/mockito/internal/exceptions/Reporter.java @@ -16,7 +16,21 @@ import org.mockito.exceptions.base.MockitoAssertionError; import org.mockito.exceptions.base.MockitoException; -import org.mockito.exceptions.misusing.*; +import org.mockito.exceptions.misusing.CannotStubVoidMethodWithReturnValue; +import org.mockito.exceptions.misusing.CannotVerifyStubOnlyMock; +import org.mockito.exceptions.misusing.FriendlyReminderException; +import org.mockito.exceptions.misusing.InjectMocksException; +import org.mockito.exceptions.misusing.InvalidUseOfMatchersException; +import org.mockito.exceptions.misusing.MissingMethodInvocationException; +import org.mockito.exceptions.misusing.NotAMockException; +import org.mockito.exceptions.misusing.NullInsteadOfMockException; +import org.mockito.exceptions.misusing.PotentialStubbingProblem; +import org.mockito.exceptions.misusing.RedundantListenerException; +import org.mockito.exceptions.misusing.UnfinishedMockingSessionException; +import org.mockito.exceptions.misusing.UnfinishedStubbingException; +import org.mockito.exceptions.misusing.UnfinishedVerificationException; +import org.mockito.exceptions.misusing.UnnecessaryStubbingException; +import org.mockito.exceptions.misusing.WrongTypeOfReturnValue; import org.mockito.exceptions.verification.MoreThanAllowedActualInvocations; import org.mockito.exceptions.verification.NeverWantedButInvoked; import org.mockito.exceptions.verification.NoInteractionsWanted; @@ -726,7 +740,7 @@ public static MockitoException invocationListenerThrewException(InvocationListen "threw an exception : " + listenerThrowable.getClass().getName() + listenerThrowable.getMessage()), listenerThrowable); } - public static MockitoException cannotInjectDependency(Field field, Object matchingMock, Exception details) { + public static MockitoException cannotInjectDependency(Field field, Object matchingMock, Throwable details) { return new MockitoException(join( "Mockito couldn't inject mock dependency '" + MockUtil.getMockName(matchingMock) + "' on field ", "'" + field + "'", @@ -736,7 +750,7 @@ public static MockitoException cannotInjectDependency(Field field, Object matchi ), details); } - private static String exceptionCauseMessageIfAvailable(Exception details) { + private static String exceptionCauseMessageIfAvailable(Throwable details) { if (details.getCause() == null) { return details.getMessage(); } diff --git a/src/main/java/org/mockito/internal/util/reflection/ConstructorResolver.java b/src/main/java/org/mockito/internal/util/reflection/ConstructorResolver.java new file mode 100644 index 0000000000..2e1fc1002a --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/ConstructorResolver.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection; + + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import org.mockito.exceptions.base.MockitoException; +import org.mockito.internal.util.MockUtil; + +/** + * Represents the strategy used to resolve a constructor and its arguments. + */ +public interface ConstructorResolver { + + /** + * Indicates whether this resolver is able to resolve a constructor and its arguments, based on + * type to resolve and resolver strategy. + * + * @return {@code true} if a constructor and its arguments are resolvable, {@code false} + * otherwise. + * @throws MockitoException implementation may choose to throw if contructor and/or its + * arguments are not resolvable for some reason. + */ + boolean isResolvable() throws MockitoException; + + /** + * Resolves constructor. + *

+ * If {@link #isResolvable()} is {@code true} this method must return a constructor. + *

+ * + * @return constructor, should not be null. + * @throws MockitoException implementation can throw if contructor is not resolvable for some + * reason (when {only @link #isResolvable()} is not {@code true}). + */ + Constructor resolveConstructor() throws MockitoException; + + /** + * Resolves constructor arguments instances matching parameters of constructor resolved by + * {@link #resolveConstructor()}. + *

+ * If {@link #isResolvable()} is {@code true} this method must return correct arguments. + *

+ * + * @return array of argument instances to be given to the constructor, should not be null. + * @throws MockitoException implementation can throw if arguments are not resolvable for some + * reason (only when {@link #isResolvable()} is not {@code true}). + */ + Object[] resolveArguments() throws MockitoException; + + /** + * Resolves the no-arguments constructor (or default constructor) of the given type. + *

+ * This resolver throws exception if given type has only parameterized constructors. + *

+ */ + class NoArgsConstructorResolver implements ConstructorResolver { + protected final Class type; + private Constructor constructor; + + /** + * Prepares Resolver to resolve constructor of given type. + * + * @param type class to resolve constructor + */ + public NoArgsConstructorResolver(Class type) { + this.type = type; + } + + /** + * @return {@code true} if there is a no-arguments constructor, throws otherwise. + * @throws MockitoException throws if given type has only parameterized constructors. + */ + @Override + public boolean isResolvable() throws MockitoException { + return resolveConstructor() != null; + } + + /** + * @return the no-arguments constructor. + * @throws MockitoException throws if given type has only parameterized constructors. + */ + @Override + public Constructor resolveConstructor() throws MockitoException { + if (constructor == null) { + try { + constructor = type.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new MockitoException("the type '" + type.getSimpleName() + "' has no default constructor", e); + } + } + return constructor; + } + + /** + * @return an empty array matching no-arguments constructor. + */ + @Override + public Object[] resolveArguments() { + return new Object[0]; + } + } + + /** + * Resolves the no-arguments constructor (or default constructor) of the given type. + *

+ * {@link #isResolvable()} returning {@code false} indicates resolver cannot find no-arguments + * constructor. + *

+ */ + class LenientNoArgsConstructorResolver extends NoArgsConstructorResolver { + + /** + * Prepares Resolver to resolve constructor of given type. + * + * @param type class to resolve constructor + */ + public LenientNoArgsConstructorResolver(Class type) { + super(type); + } + + /** + * @return {@code true} if there is a no-arguments constructor, {@code false} otherwise. + */ + @Override + public boolean isResolvable() { + try { + return resolveConstructor() != null; + } catch (MockitoException e) { + return false; + } + } + } + + /** + * Resolves the constructor with the highest number of parameters and, in case of egality, the + * highest number of mockable parameters.
+ * Try to resolve actual constuctor arguments instances with given mocks matching constructor + * parameters, or null if there is no mocks matching a parameter. + * + *
+ * TODO on missing mock type, shall it abandon or create "noname" mocks. + * TODO and what if the arg type is not mockable. + *
+ */ + class BiggestConstructorResolver implements ConstructorResolver { + protected final Class type; + protected final Set mocks; + + protected final Comparator> byParameterNumber = new Comparator>() { + public int compare(Constructor constructorA, Constructor constructorB) { + int argLengths = constructorB.getParameterTypes().length - constructorA.getParameterTypes().length; + if (argLengths == 0) { + int constructorAMockableParamsSize = countMockableParams(constructorA); + int constructorBMockableParamsSize = countMockableParams(constructorB); + return constructorBMockableParamsSize - constructorAMockableParamsSize; + } + return argLengths; + } + + private int countMockableParams(Constructor constructor) { + int constructorMockableParamsSize = 0; + for (Class aClass : constructor.getParameterTypes()) { + if (MockUtil.typeMockabilityOf(aClass).mockable()) { + constructorMockableParamsSize++; + } + } + return constructorMockableParamsSize; + } + }; + + private Constructor constructor; + private Object[] arguments; + + /** + * Prepares Resolver to resolve constructor of given type and resolve constuctor arguments + * with given mocks. + * + * @param type class to resolve constructor + * @param mocks set of mocks instances + */ + public BiggestConstructorResolver(Class type, Set mocks) { + this.type = type; + this.mocks = mocks; + } + + /** + * @return {@code true} if there is a parameterized constructor, throws otherwise. + * @throws MockitoException throws if given type has only no-arguments constructor. + */ + @Override + public boolean isResolvable() throws MockitoException { + return resolveConstructor() != null; + } + + /** + * @return constructor with the highest number of parameters and, in case of egality, the + * highest number of mockable parameters. + * @throws MockitoException throws if given type has only no-arguments constructor. + */ + @Override + public Constructor resolveConstructor() throws MockitoException { + if (constructor == null) { + final List> constructors = Arrays.asList(type.getDeclaredConstructors()); + Collections.sort(constructors, byParameterNumber); + final Constructor constructor = constructors.get(0); + checkParameterized(constructor); + this.constructor = constructor; + } + return constructor; + } + + /** + * @return an array of actual argument instances of given mocks matching constructor + * parameters, or null if there is no mocks matching a parameter. + */ + @Override + public Object[] resolveArguments() { + if (arguments == null) { + final Class[] argTypes = resolveConstructor().getParameterTypes(); + final List argumentInstances = new ArrayList(argTypes.length); + for (Class argType : argTypes) { + argumentInstances.add(objectThatIsAssignableFrom(argType)); + } + arguments = argumentInstances.toArray(); + } + return arguments; + } + + protected Object objectThatIsAssignableFrom(Class argType) { + for (Object object : mocks) { + if (object != null && argType.isAssignableFrom(object.getClass())) { + return object; + } + } + return null; + } + + protected void checkParameterized(Constructor constructor) { + if (constructor.getParameterTypes().length == 0) { + throw new MockitoException("the type '" + type.getSimpleName() + "' has no parameterized constructor"); + } + } + } + + /** + * Resolves the constructor with the highest number of parameters and, in case of egality, the + * highest number of mockable parameters.
+ * Resolves actual constructor arguments instances with given mocks matching constructor + * parameters, or indicates there is no resolvable constructors if there is no mocks matching a + * parameter. + */ + class StrictBiggestConstructorResolver extends BiggestConstructorResolver { + + private boolean hasNullArguments = false; + + /** + * Prepares Resolver to resolve constructor of given type and resolve constuctor arguments + * with given mocks. + * + * @param type class to resolve constructor + * @param mocks set of mocks instances + */ + public StrictBiggestConstructorResolver(Class type, Set mocks) { + super(type, mocks); + } + + /** + * @return {@code true} if given mocks matches all arguments of constructor with the highest + * number of parameters, {@code false} otherwise. + * @throws MockitoException throws if given type has only no-arguments constructor. + */ + @Override + public boolean isResolvable() throws MockitoException { + resolveArguments(); + return !hasNullArguments; + } + + @Override + protected Object objectThatIsAssignableFrom(Class argType) { + final Object object = super.objectThatIsAssignableFrom(argType); + if (object == null) { + hasNullArguments = true; + } + return object; + } + } + +} diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldInitializationReport.java b/src/main/java/org/mockito/internal/util/reflection/FieldInitializationReport.java index 957832ee00..035e4fd8ff 100644 --- a/src/main/java/org/mockito/internal/util/reflection/FieldInitializationReport.java +++ b/src/main/java/org/mockito/internal/util/reflection/FieldInitializationReport.java @@ -10,12 +10,18 @@ public class FieldInitializationReport { private final Object fieldInstance; private final boolean wasInitialized; - private final boolean wasInitializedUsingConstructorArgs; - public FieldInitializationReport(Object fieldInstance, boolean wasInitialized, boolean wasInitializedUsingConstructorArgs) { + /** + * Report field is not initialized. + */ + public FieldInitializationReport() { + this.fieldInstance = null; + this.wasInitialized = false; + } + + public FieldInitializationReport(Object fieldInstance, boolean wasInitialized) { this.fieldInstance = fieldInstance; this.wasInitialized = wasInitialized; - this.wasInitializedUsingConstructorArgs = wasInitializedUsingConstructorArgs; } /** @@ -28,21 +34,21 @@ public Object fieldInstance() { } /** - * Indicate whether the field was created during the process or not. + * Indicate whether the field is initialized or not. * - * @return true if created, false if the field did already hold an instance. + * @return true if field is initialized, false otherwise. */ - public boolean fieldWasInitialized() { - return wasInitialized; + public boolean fieldIsInitialized() { + return fieldInstance != null; } /** - * Indicate whether the field was created using constructor args. + * Indicate whether the field was created during the process or not. * - * @return true if field was created using constructor parameters. + * @return true if created, false if the field did already hold an instance. */ - public boolean fieldWasInitializedUsingContructorArgs() { - return wasInitializedUsingConstructorArgs; + public boolean fieldWasInitialized() { + return wasInitialized; } /** diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java b/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java index e247d18c13..b947d8c20d 100644 --- a/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java +++ b/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java @@ -12,16 +12,12 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; import org.mockito.exceptions.base.MockitoException; -import org.mockito.internal.util.MockUtil; /** - * Initialize a field with type instance if a default constructor can be found. + * Initialize a field with type instance if a constructor can be found by the strategy of given + * {@link ConstructorResolver}. * *

* If the given field is already initialized, then the actual instance is returned. @@ -33,8 +29,7 @@ public class FieldInitializer { private final Object fieldOwner; private final Field field; - private final ConstructorInstantiator instantiator; - + private final ConstructorResolver constructorResolver; /** * Prepare initializer with the given field on the given instance. @@ -45,38 +40,19 @@ public class FieldInitializer { * * @param fieldOwner Instance of the test. * @param field Field to be initialize. + * @param constructorResolver Constructor resolver strategy */ - public FieldInitializer(Object fieldOwner, Field field) { - this(fieldOwner, field, new NoArgConstructorInstantiator(fieldOwner, field)); - } - - /** - * Prepare initializer with the given field on the given instance. - * - *

- * This constructor fail fast if the field type cannot be handled. - *

- * - * @param fieldOwner Instance of the test. - * @param field Field to be initialize. - * @param argResolver Constructor parameters resolver - */ - public FieldInitializer(Object fieldOwner, Field field, ConstructorArgumentResolver argResolver) { - this(fieldOwner, field, new ParameterizedConstructorInstantiator(fieldOwner, field, argResolver)); - } - - private FieldInitializer(Object fieldOwner, Field field, ConstructorInstantiator instantiator) { + public FieldInitializer(Object fieldOwner, Field field, ConstructorResolver constructorResolver) { if(new FieldReader(fieldOwner, field).isNull()) { checkNotLocal(field); checkNotInner(field); checkNotInterface(field); checkNotEnum(field); checkNotAbstract(field); - } this.fieldOwner = fieldOwner; this.field = field; - this.instantiator = instantiator; + this.constructorResolver = constructorResolver; } /** @@ -132,173 +108,43 @@ private void checkNotEnum(Field field) { private FieldInitializationReport acquireFieldInstance() throws IllegalAccessException { Object fieldInstance = field.get(fieldOwner); if(fieldInstance != null) { - return new FieldInitializationReport(fieldInstance, false, false); - } - - return instantiator.instantiate(); - } - - /** - * Represents the strategy used to resolve actual instances - * to be given to a constructor given the argument types. - */ - public interface ConstructorArgumentResolver { - - /** - * Try to resolve instances from types. - * - *

- * Checks on the real argument type or on the correct argument number - * will happen during the field initialization {@link FieldInitializer#initialize()}. - * I.e the only responsibility of this method, is to provide instances if possible. - *

- * - * @param argTypes Constructor argument types, should not be null. - * @return The argument instances to be given to the constructor, should not be null. - */ - Object[] resolveTypeInstances(Class... argTypes); - } - - private interface ConstructorInstantiator { - FieldInitializationReport instantiate(); - } - - /** - * Constructor instantiating strategy for no-arg constructor. - * - *

- * If a no-arg constructor can be found then the instance is created using - * this constructor. - * Otherwise a technical MockitoException is thrown. - *

- */ - static class NoArgConstructorInstantiator implements ConstructorInstantiator { - private final Object testClass; - private final Field field; - - /** - * Internal, checks are done by FieldInitializer. - * Fields are assumed to be accessible. - */ - NoArgConstructorInstantiator(Object testClass, Field field) { - this.testClass = testClass; - this.field = field; + return new FieldInitializationReport(fieldInstance, false); } - public FieldInitializationReport instantiate() { - final AccessibilityChanger changer = new AccessibilityChanger(); - Constructor constructor = null; - try { - constructor = field.getType().getDeclaredConstructor(); - changer.enableAccess(constructor); - - final Object[] noArg = new Object[0]; - Object newFieldInstance = constructor.newInstance(noArg); - setField(testClass, field,newFieldInstance); - - return new FieldInitializationReport(field.get(testClass), true, false); - } catch (NoSuchMethodException e) { - throw new MockitoException("the type '" + field.getType().getSimpleName() + "' has no default constructor", e); - } catch (InvocationTargetException e) { - throw new MockitoException("the default constructor of type '" + field.getType().getSimpleName() + "' has raised an exception (see the stack trace for cause): " + e.getTargetException().toString(), e); - } catch (InstantiationException e) { - throw new MockitoException("InstantiationException (see the stack trace for cause): " + e.toString(), e); - } catch (IllegalAccessException e) { - throw new MockitoException("IllegalAccessException (see the stack trace for cause): " + e.toString(), e); - } finally { - if(constructor != null) { - changer.safelyDisableAccess(constructor); - } - } - } + return instantiate(); } - /** - * Constructor instantiating strategy for parameterized constructors. - * - *

- * Choose the constructor with the highest number of parameters, then - * call the ConstructorArgResolver to get actual argument instances. - * If the argResolver fail, then a technical MockitoException is thrown is thrown. - * Otherwise the instance is created with the resolved arguments. - *

- */ - static class ParameterizedConstructorInstantiator implements ConstructorInstantiator { - private final Object testClass; - private final Field field; - private final ConstructorArgumentResolver argResolver; - private final Comparator> byParameterNumber = new Comparator>() { - public int compare(Constructor constructorA, Constructor constructorB) { - int argLengths = constructorB.getParameterTypes().length - constructorA.getParameterTypes().length; - if (argLengths == 0) { - int constructorAMockableParamsSize = countMockableParams(constructorA); - int constructorBMockableParamsSize = countMockableParams(constructorB); - return constructorBMockableParamsSize - constructorAMockableParamsSize; - } - return argLengths; - } - - private int countMockableParams(Constructor constructor) { - int constructorMockableParamsSize = 0; - for (Class aClass : constructor.getParameterTypes()) { - if(MockUtil.typeMockabilityOf(aClass).mockable()){ - constructorMockableParamsSize++; - } - } - return constructorMockableParamsSize; - } - }; - - /** - * Internal, checks are done by FieldInitializer. - * Fields are assumed to be accessible. - */ - ParameterizedConstructorInstantiator(Object testClass, Field field, ConstructorArgumentResolver argumentResolver) { - this.testClass = testClass; - this.field = field; - this.argResolver = argumentResolver; - } - - public FieldInitializationReport instantiate() { - final AccessibilityChanger changer = new AccessibilityChanger(); - Constructor constructor = null; - try { - constructor = biggestConstructor(field.getType()); - changer.enableAccess(constructor); - - final Object[] args = argResolver.resolveTypeInstances(constructor.getParameterTypes()); - Object newFieldInstance = constructor.newInstance(args); - setField(testClass, field,newFieldInstance); - - return new FieldInitializationReport(field.get(testClass), false, true); - } catch (IllegalArgumentException e) { - throw new MockitoException("internal error : argResolver provided incorrect types for constructor " + constructor + " of type " + field.getType().getSimpleName(), e); - } catch (InvocationTargetException e) { - throw new MockitoException("the constructor of type '" + field.getType().getSimpleName() + "' has raised an exception (see the stack trace for cause): " + e.getTargetException().toString(), e); - } catch (InstantiationException e) { - throw new MockitoException("InstantiationException (see the stack trace for cause): " + e.toString(), e); - } catch (IllegalAccessException e) { - throw new MockitoException("IllegalAccessException (see the stack trace for cause): " + e.toString(), e); - } finally { - if(constructor != null) { - changer.safelyDisableAccess(constructor); - } + private FieldInitializationReport instantiate() { + final AccessibilityChanger changer = new AccessibilityChanger(); + Constructor constructor = null; + try { + if (!constructorResolver.isResolvable()) { + return new FieldInitializationReport(); } - } - private void checkParameterized(Constructor constructor, Field field) { - if(constructor.getParameterTypes().length == 0) { - throw new MockitoException("the field " + field.getName() + " of type " + field.getType() + " has no parameterized constructor"); + constructor = constructorResolver.resolveConstructor(); + changer.enableAccess(constructor); + + final Object[] args = constructorResolver.resolveArguments(); + Object newFieldInstance = constructor.newInstance(args); + setField(fieldOwner, field, newFieldInstance); + + return new FieldInitializationReport(newFieldInstance, true); + } catch (MockitoException e) { + throw new MockitoException("field '" + field.getName() + "': " + e.getMessage(), e); + } catch (IllegalArgumentException e) { + throw new MockitoException("internal error : " + constructorResolver.getClass().getSimpleName() + " provided incorrect types for constructor " + constructor + " of type " + field.getType().getSimpleName(), e); + } catch (InvocationTargetException e) { + throw new MockitoException("the constructor of type '" + field.getType().getSimpleName() + "' has raised an exception (see the stack trace for cause): " + e.getTargetException().toString(), e); + } catch (InstantiationException e) { + throw new MockitoException("InstantiationException (see the stack trace for cause): " + e.toString(), e); + } catch (IllegalAccessException e) { + throw new MockitoException("IllegalAccessException (see the stack trace for cause): " + e.toString(), e); + } finally { + if(constructor != null) { + changer.safelyDisableAccess(constructor); } } - - private Constructor biggestConstructor(Class clazz) { - final List> constructors = Arrays.asList(clazz.getDeclaredConstructors()); - Collections.sort(constructors, byParameterNumber); - - Constructor constructor = constructors.get(0); - checkParameterized(constructor, field); - return constructor; - } } + } diff --git a/src/test/java/org/mockito/internal/configuration/injection/ConstructorInjectionTest.java b/src/test/java/org/mockito/internal/configuration/injection/ConstructorInjectionTest.java index 40231607a8..35a85689b7 100644 --- a/src/test/java/org/mockito/internal/configuration/injection/ConstructorInjectionTest.java +++ b/src/test/java/org/mockito/internal/configuration/injection/ConstructorInjectionTest.java @@ -1,11 +1,13 @@ /* - * Copyright (c) 2007 Mockito contributors + * Copyright (c) 2020 Mockito contributors * This program is made available under the terms of the MIT License. */ package org.mockito.internal.configuration.injection; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.lang.reflect.Field; import java.util.HashSet; @@ -16,15 +18,18 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.exceptions.base.MockitoException; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class ConstructorInjectionTest { @Mock private Observer observer; - private ArgConstructor whatever; + public ArgConstructor whatever; + public NoArgsConstructor noArgsConstructor; + public ThrowingConstructor throwingConstructor; - private ConstructorInjection underTest; + protected ConstructorInjection underTest; @Before public void initialize_dependencies() { @@ -37,6 +42,35 @@ public void should_do_the_trick_of_instantiating() throws Exception { assertTrue(result); assertNotNull(whatever); + assertThat(whatever.observer).isEqualTo(observer); + } + + @Test + public void should_instantiate_with_null_arg() throws Exception { + boolean result = underTest.process(field("whatever"), this, new HashSet()); + + assertThat(result).isTrue(); + assertThat(whatever).isNotNull(); + assertThat(whatever.observer).isNull(); + } + + @Test + public void should_not_instantiate_no_args_constructor() throws Exception { + boolean result = underTest.process(field("noArgsConstructor"), this, newSetOf(observer)); + + assertThat(result).isFalse(); + assertThat(noArgsConstructor).isNull(); + } + + @Test + public void should_fail_to_instantiate_throwing_constructor() throws Exception { + try { + underTest.process(field("throwingConstructor"), this, newSetOf(observer)); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("Cannot instantiate").contains("throwingConstructor").contains("ThrowingConstructor").contains("business logic failed"); + } + assertThat(throwingConstructor).isNull(); } private Set newSetOf(Object item) { @@ -45,11 +79,25 @@ private Set newSetOf(Object item) { return mocks; } - private Field field(String fieldName) throws NoSuchFieldException { - return this.getClass().getDeclaredField(fieldName); + protected Field field(String fieldName) throws NoSuchFieldException { + return this.getClass().getField(fieldName); } private static class ArgConstructor { - ArgConstructor(Observer observer) {} + private final Observer observer; + ArgConstructor(Observer observer) { + this.observer = observer; + } + } + + private static class NoArgsConstructor { + NoArgsConstructor() {} + } + + private static class ThrowingConstructor { + ThrowingConstructor(Observer observer) throws Exception { + throw new NullPointerException("business logic failed"); + } } + } diff --git a/src/test/java/org/mockito/internal/configuration/injection/LenientPropertyAndSetterInjectionTest.java b/src/test/java/org/mockito/internal/configuration/injection/LenientPropertyAndSetterInjectionTest.java new file mode 100644 index 0000000000..1c41d7cd60 --- /dev/null +++ b/src/test/java/org/mockito/internal/configuration/injection/LenientPropertyAndSetterInjectionTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.configuration.injection; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashSet; + +import org.junit.Before; +import org.junit.Test; + +public class LenientPropertyAndSetterInjectionTest extends PropertyAndSetterInjectionTest { + + @Before + @Override + public void setup() { + underTest = new LenientPropertyAndSetterInjection(); + } + + @Override + public void should_fail_to_instantiate_one_arg_constructor() throws Exception { + // This test is not relevant for LenientPropertyAndSetterInjectionTest + } + + @Test + public void should_not_instantiate_one_arg_constructor() throws Exception { + boolean result = underTest.process(field("oneArgConstructor"), this, new HashSet()); + + assertThat(result).isFalse(); + assertThat(defaultConstructor).isNull(); + } + + @Override + public void should_fail_to_instantiate_vararg_constructor() throws Exception { + // This test is not relevant for LenientPropertyAndSetterInjectionTest + } + + @Test + public void should_not_instantiate_vararg_constructor() throws Exception { + boolean result = underTest.process(field("varargConstructor"), this, new HashSet()); + + assertThat(result).isFalse(); + assertThat(defaultConstructor).isNull(); + } + +} diff --git a/src/test/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjectionTest.java b/src/test/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjectionTest.java new file mode 100644 index 0000000000..76a4ba6ddd --- /dev/null +++ b/src/test/java/org/mockito/internal/configuration/injection/PropertyAndSetterInjectionTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.configuration.injection; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Observer; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.exceptions.base.MockitoException; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class PropertyAndSetterInjectionTest { + + @Mock + private Observer whatever; + @Mock + private Observer whichever; + + public DefaultConstructor defaultConstructor; + public NoArgsConstructor noArgsConstructor; + public OneArgConstructor oneArgConstructor; + public VarargConstructor varargConstructor; + public ThrowingConstructor throwingConstructor; + public ThrowingSetter throwingSetter; + + protected PropertyAndSetterInjection underTest; + + @Before + public void setup() { + underTest = new PropertyAndSetterInjection(); + } + + @Test + public void should_instantiate_default_constructor() throws Exception { + boolean result = underTest.process(field("defaultConstructor"), this, newSetOf(whatever, whichever)); + + assertThat(result).isTrue(); + assertThat(defaultConstructor).isNotNull(); + + } + + @Test + public void should_instantiate_no_args_constructor() throws Exception { + boolean result = underTest.process(field("noArgsConstructor"), this, newSetOf(whatever)); + + assertThat(result).isTrue(); + assertThat(noArgsConstructor).isNotNull(); + } + + @Test + public void should_instantiate_but_not_initialize_fields() throws Exception { + boolean result = underTest.process(field("defaultConstructor"), this, new HashSet()); + + assertThat(result).isFalse(); + assertThat(defaultConstructor).isNotNull(); + assertThat(defaultConstructor.whatever).isNull(); + assertThat(defaultConstructor.other).isNull(); + } + + @Test + public void should_fail_to_instantiate_one_arg_constructor() throws Exception { + try { + underTest.process(field("oneArgConstructor"), this, newSetOf(whatever)); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("Cannot instantiate").contains("oneArgConstructor").contains("OneArgConstructor").contains("has no default constructor"); + } + assertThat(throwingConstructor).isNull(); + } + + @Test + public void should_fail_to_instantiate_vararg_constructor() throws Exception { + try { + underTest.process(field("varargConstructor"), this, newSetOf(whatever)); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("Cannot instantiate").contains("varargConstructor").contains("VarargConstructor").contains("has no default constructor"); + } + assertThat(throwingConstructor).isNull(); + } + + @Test + public void should_fail_to_instantiate_throwing_constructor() throws Exception { + try { + underTest.process(field("throwingConstructor"), this, newSetOf(whatever)); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("Cannot instantiate").contains("throwingConstructor").contains("ThrowingConstructor").contains("business logic failed"); + } + assertThat(throwingConstructor).isNull(); + } + + @Test + public void should_initialize_fields_by_setter() throws Exception { + boolean result = underTest.process(field("defaultConstructor"), this, newSetOf(whatever)); + + assertThat(result).isTrue(); + assertThat(defaultConstructor).isNotNull(); + assertThat(defaultConstructor.whatever).isEqualTo(whatever); + assertThat(defaultConstructor.other).isEqualTo(whatever); + + } + + @Test + public void should_initialize_fields_by_property() throws Exception { + boolean result = underTest.process(field("noArgsConstructor"), this, newSetOf(whatever, whichever)); + + assertThat(result).isTrue(); + assertThat(noArgsConstructor).isNotNull(); + assertThat(noArgsConstructor.whatever).isEqualTo(whatever); + assertThat(noArgsConstructor.whichever).isEqualTo(whichever); + + } + + @Test + public void should_fail_to_initialize_fields_by_throwing_setter() throws Exception { + try { + underTest.process(field("throwingSetter"), this, newSetOf(whatever)); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("couldn't inject mock").contains("whatever").contains("Observer").contains("business logic failed"); + } + assertThat(throwingSetter).isNotNull(); + assertThat(throwingSetter.whatever).isNull(); + } + + private Set newSetOf(Object... mocks) { + return new HashSet(Arrays.asList(mocks)); + } + + protected Field field(String fieldName) throws NoSuchFieldException { + return this.getClass().getField(fieldName); + } + + private static class NoArgsConstructor { + private Observer whatever; + private Observer whichever; + NoArgsConstructor() { + } + } + + public static class DefaultConstructor { + private Observer whatever; + private Observer other; + public void setWhatever(Observer whatever) { + this.whatever = whatever; + this.other = whatever; + } + } + + static class OneArgConstructor { + public OneArgConstructor(String whatever) { + } + } + + static class VarargConstructor { + VarargConstructor(String... whatever) { + } + } + + static class ThrowingConstructor { + ThrowingConstructor() { + throw new NullPointerException("business logic failed"); + } + } + + static class ThrowingSetter { + private Observer whatever; + public void setWhatever(Observer whatever) { + throw new NullPointerException("business logic failed"); + } + } + +} diff --git a/src/test/java/org/mockito/internal/configuration/injection/SimpleArgumentResolverTest.java b/src/test/java/org/mockito/internal/configuration/injection/SimpleArgumentResolverTest.java deleted file mode 100644 index 70f5b497f3..0000000000 --- a/src/test/java/org/mockito/internal/configuration/injection/SimpleArgumentResolverTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2007 Mockito contributors - * This program is made available under the terms of the MIT License. - */ -package org.mockito.internal.configuration.injection; - -import static org.junit.Assert.*; - -import java.io.ByteArrayOutputStream; -import java.io.OutputStream; -import java.util.*; - -import org.junit.Test; - -public class SimpleArgumentResolverTest { - - @Test - public void should_return_object_matching_given_types() throws Exception { - ConstructorInjection.SimpleArgumentResolver resolver = - new ConstructorInjection.SimpleArgumentResolver(newSetOf(new HashSet(), new ByteArrayOutputStream(), new HashMap())); - - Object[] resolvedInstance = resolver.resolveTypeInstances(Set.class, Map.class, OutputStream.class); - - assertEquals(3, resolvedInstance.length); - assertTrue(resolvedInstance[0] instanceof Set); - assertTrue(resolvedInstance[1] instanceof Map); - assertTrue(resolvedInstance[2] instanceof OutputStream); - } - - @Test - public void should_return_null_when_match_is_not_possible_on_given_types() throws Exception { - ConstructorInjection.SimpleArgumentResolver resolver = - new ConstructorInjection.SimpleArgumentResolver(newSetOf(new HashSet(), new ByteArrayOutputStream())); - - Object[] resolvedInstance = resolver.resolveTypeInstances(Set.class, Map.class, OutputStream.class); - - assertEquals(3, resolvedInstance.length); - assertTrue(resolvedInstance[0] instanceof Set); - assertNull(resolvedInstance[1]); - assertTrue(resolvedInstance[2] instanceof OutputStream); - } - - @Test - public void should_return_null_when_types_are_primitives() throws Exception { - ConstructorInjection.SimpleArgumentResolver resolver = - new ConstructorInjection.SimpleArgumentResolver(newSetOf(new HashMap(), new TreeSet())); - - Object[] resolvedInstance = resolver.resolveTypeInstances(Set.class, Map.class, Boolean.class); - - assertEquals(3, resolvedInstance.length); - assertTrue(resolvedInstance[0] instanceof Set); - assertTrue(resolvedInstance[1] instanceof Map); - assertNull(resolvedInstance[2]); - } - - private Set newSetOf(Object... objects) { - return new HashSet(Arrays.asList(objects)); - } - - -} diff --git a/src/test/java/org/mockito/internal/configuration/injection/StrictConstructorInjectionTest.java b/src/test/java/org/mockito/internal/configuration/injection/StrictConstructorInjectionTest.java new file mode 100644 index 0000000000..a86ff0a37a --- /dev/null +++ b/src/test/java/org/mockito/internal/configuration/injection/StrictConstructorInjectionTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.configuration.injection; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashSet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class StrictConstructorInjectionTest extends ConstructorInjectionTest { + + @Before + public void initialize_dependencies() { + underTest = new StrictConstructorInjection(); + } + + @Override + public void should_instantiate_with_null_arg() throws Exception { + // This test is not relevant for StrictConstructorInjection + } + + @Test + public void should_not_instantiate_with_null_arg() throws Exception { + boolean result = underTest.process(field("whatever"), this, new HashSet()); + + assertThat(result).isFalse(); + assertThat(whatever).isNull(); + } + +} diff --git a/src/test/java/org/mockito/internal/util/reflection/BiggestConstructorResolverTest.java b/src/test/java/org/mockito/internal/util/reflection/BiggestConstructorResolverTest.java new file mode 100644 index 0000000000..0d5e9b9900 --- /dev/null +++ b/src/test/java/org/mockito/internal/util/reflection/BiggestConstructorResolverTest.java @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2020 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Observer; +import java.util.Set; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.exceptions.base.MockitoException; +import org.mockito.internal.util.reflection.ConstructorResolver.BiggestConstructorResolver; +import org.mockito.junit.MockitoJUnitRunner; + +@SuppressWarnings("unchecked") +@RunWith(MockitoJUnitRunner.class) +public class BiggestConstructorResolverTest { + + @Test + public void type_should_be_instantiable_if_resolver_provide_matching_types() throws Exception { + Observer observer = mock(Observer.class); + Map map = mock(Map.class); + + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(MultipleConstructor.class, observer, map); + final Constructor constructor = resolver.resolveConstructor(); + final Object[] arguments = resolver.resolveArguments(); + final Object newInstance = constructor.newInstance(arguments); + + assertThat(newInstance).isNotNull().isInstanceOf(MultipleConstructor.class); + final MultipleConstructor multipleConstructor = (MultipleConstructor) newInstance; + assertThat(multipleConstructor.observer).isNotNull().isEqualTo(observer); + assertThat(multipleConstructor.map).isNotNull().isEqualTo(map); + } + + @Test + public void type_should_be_instantiable_with_null_if_an_argument_instance_type_do_not_match_wanted_type() throws Exception { + Observer observer = mock(Observer.class); + Set wrongArg = mock(Set.class); + + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(MultipleConstructor.class, observer, wrongArg); + final Constructor constructor = resolver.resolveConstructor(); + final Object[] arguments = resolver.resolveArguments(); + final Object newInstance = constructor.newInstance(arguments); + + assertThat(newInstance).isNotNull().isInstanceOf(MultipleConstructor.class); + final MultipleConstructor multipleConstructor = (MultipleConstructor) newInstance; + assertThat(multipleConstructor.observer).isNotNull().isEqualTo(observer); + assertThat(multipleConstructor.map).isNull(); + } + + @Test + public void type_should_be_instantiable_with_vararg_constructor() throws Exception { + Observer[] vararg = new Observer[] { }; + String string = ""; + + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(VarargConstructor.class, string, vararg); + final Constructor constructor = resolver.resolveConstructor(); + final Object[] arguments = resolver.resolveArguments(); + final Object newInstance = constructor.newInstance(arguments); + + assertThat(newInstance).isNotNull().isInstanceOf(VarargConstructor.class); + final VarargConstructor varargConstructor = (VarargConstructor) newInstance; + assertThat(varargConstructor.whatever).isNotNull().isEqualTo(string); + assertThat(varargConstructor.observers).isNotNull().isEqualTo(vararg); + } + + @Test + public void type_should_be_instantiable_with_wrapper_constructor() throws Exception { + final HashSet hashSet = mock(HashSet.class); + final HashMap hashMap = mock(HashMap.class); + + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(WrapperConstructor.class, hashSet, hashMap); + final Constructor constructor = resolver.resolveConstructor(); + final Object[] arguments = resolver.resolveArguments(); + final Object newInstance = constructor.newInstance(arguments); + + assertThat(newInstance).isNotNull().isInstanceOf(WrapperConstructor.class); + final WrapperConstructor wrapperConstructor = (WrapperConstructor) newInstance; + assertThat(wrapperConstructor.set).isNotNull().isEqualTo(hashSet); + assertThat(wrapperConstructor.map).isNotNull().isEqualTo(hashMap); + assertThat(wrapperConstructor.bool).isNull(); + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_to_instantiate_type_with_primitive_constructor() throws Exception { + final HashSet hashSet = mock(HashSet.class); + final HashMap hashMap = mock(HashMap.class); + + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(PrimitiveConstructor.class, hashSet, hashMap); + final Constructor constructor = resolver.resolveConstructor(); + final Object[] arguments = resolver.resolveArguments(); + constructor.newInstance(arguments); + } + + @Test + public void is_resolvable_should_return_true_if_type_has_single_parameterized_contructor() throws Exception { + Observer observer = mock(Observer.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(OneConstructor.class, observer); + + assertThat(resolver.isResolvable()).isTrue(); + } + + @Test + public void is_resolvable_should_return_true_if_type_has_parameterized_contructor() throws Exception { + final Observer observer = mock(Observer.class); + final HashMap hashMap = mock(HashMap.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(MultipleConstructor.class, observer, hashMap); + + assertThat(resolver.isResolvable()).isTrue(); + } + + @Test + public void is_resolvable_should_return_true_with_vararg_constructor() throws Exception { + Observer[] vararg = new Observer[] { }; + String string = ""; + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(VarargConstructor.class, vararg, string); + + assertThat(resolver.isResolvable()).isTrue(); + } + + @Test + public void is_resolvable_should_return_true_when_match_is_not_possible_on_given_type() throws Exception { + final HashMap hashMap = mock(HashMap.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(MultipleConstructor.class, hashMap); + + assertThat(resolver.isResolvable()).isTrue(); + } + + @Test + public void is_resolvable_should_return_true_when_types_are_wrappers() throws Exception { + final HashSet hashSet = mock(HashSet.class); + final HashMap hashMap = mock(HashMap.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(WrapperConstructor.class, hashSet, hashMap); + + assertThat(resolver.isResolvable()).isTrue(); + } + + @Test + public void is_resolvable_should_return_true_when_types_are_primitives() throws Exception { + final HashSet hashSet = mock(HashSet.class); + final HashMap hashMap = mock(HashMap.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(PrimitiveConstructor.class, hashSet, hashMap); + + assertThat(resolver.isResolvable()).isTrue(); + } + + @Test + public void is_resolvable_should_fail_if_no_parameterized_constructor_found() throws Exception { + try { + buildBiggestConstructorResolver(NoArgConstructor.class).isResolvable(); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("no parameterized constructor").contains("NoArgConstructor"); + } + } + + @Test + public void is_resolvable_should_fail_if_type_has_default_contructor() throws Exception { + try { + buildBiggestConstructorResolver(DefaultConstructor.class).isResolvable(); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("no parameterized constructor").contains("DefaultConstructor"); + } + } + + @Test + public void resolve_constructor_should_return_single_parameterized_constructor() throws Exception { + Observer observer = mock(Observer.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(OneConstructor.class, observer); + + final Constructor constructor = resolver.resolveConstructor(); + assertThat(constructor).isNotNull(); + assertThat(constructor.getParameterTypes()).isNotNull().hasSize(1); + } + + @Test + public void resolve_constructor_should_return_biggest_constructor() throws Exception { + final Observer observer = mock(Observer.class); + final HashMap hashMap = mock(HashMap.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(MultipleConstructor.class, observer, hashMap); + + final Constructor constructor = resolver.resolveConstructor(); + assertThat(constructor).isNotNull(); + assertThat(constructor.getParameterTypes()).isNotNull().hasSize(2); + } + + @Test + public void resolve_constructor_should_return_vararg_constructor() throws Exception { + Observer[] vararg = new Observer[] { }; + String string = ""; + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(VarargConstructor.class, vararg, string); + + final Constructor constructor = resolver.resolveConstructor(); + assertThat(constructor).isNotNull(); + assertThat(constructor.getParameterTypes()).isNotNull().hasSize(2); + } + + @Test + public void resolve_constructor_should_fail_if_no_parameterized_constructor_found() throws Exception { + try { + buildBiggestConstructorResolver(NoArgConstructor.class).resolveConstructor(); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("no parameterized constructor").contains("NoArgConstructor"); + } + } + + @Test + public void resolve_constructor_should_fail_if_type_has_default_contructor() throws Exception { + try { + buildBiggestConstructorResolver(DefaultConstructor.class).resolveConstructor(); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("no parameterized constructor").contains("DefaultConstructor"); + } + } + + @Test + public void resolve_arguments_should_return_one_matching_object() throws Exception { + Observer observer = mock(Observer.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(OneConstructor.class, observer); + + assertThat(resolver.resolveArguments()).isNotNull().hasSize(1).containsExactly(observer); + } + + @Test + public void resolve_arguments_should_return_object_matching_given_types() throws Exception { + final Observer observer = mock(Observer.class); + final HashMap hashMap = mock(HashMap.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(MultipleConstructor.class, hashMap, observer); + + assertThat(resolver.resolveArguments()).isNotNull().hasSize(2).containsExactly(observer, hashMap); + } + + @Test + public void resolve_arguments_should_return_vararg_matching_given_types() throws Exception { + Observer[] vararg = new Observer[] { }; + String string = ""; + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(VarargConstructor.class, vararg, string); + + assertThat(resolver.resolveArguments()).isNotNull().hasSize(2).containsExactly(string, vararg); + } + + @Test + public void resolve_arguments_should_return_null_when_match_is_not_possible_on_given_type() throws Exception { + final HashMap hashMap = mock(HashMap.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(MultipleConstructor.class, hashMap); + + assertThat(resolver.resolveArguments()).isNotNull().hasSize(2).containsExactly(null, hashMap); + } + + @Test + public void resolve_arguments_should_return_null_when_types_are_wrappers() throws Exception { + final HashSet hashSet = mock(HashSet.class); + final HashMap hashMap = mock(HashMap.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(WrapperConstructor.class, hashSet, hashMap); + + assertThat(resolver.resolveArguments()).isNotNull().hasSize(3).containsExactly(hashSet, hashMap, null); + } + + @Test + public void resolve_arguments_should_return_null_when_types_are_primitives() throws Exception { + final HashSet hashSet = mock(HashSet.class); + final HashMap hashMap = mock(HashMap.class); + final BiggestConstructorResolver resolver = buildBiggestConstructorResolver(PrimitiveConstructor.class, hashSet, hashMap); + + assertThat(resolver.resolveArguments()).isNotNull().hasSize(3).containsExactly(hashSet, hashMap, null); + } + + protected BiggestConstructorResolver buildBiggestConstructorResolver(Class type, Object... mocks) { + return new BiggestConstructorResolver(type, new HashSet(Arrays.asList(mocks))); + } + + private static class NoArgConstructor { + NoArgConstructor() { + } + } + + public static class DefaultConstructor { + } + + private static class OneConstructor { + public OneConstructor(Observer observer) { + } + } + + static class MultipleConstructor extends OneConstructor { + Observer observer; + Map map; + + public MultipleConstructor(Observer observer) { + this(observer, null); + } + + public MultipleConstructor(Observer observer, Map map) { + super(observer); + this.observer = observer; + this.map = map; + } + + public MultipleConstructor(Observer observer, boolean nonMockable) { + super(observer); + this.observer = observer; + } + } + + private static class VarargConstructor { + String whatever; + Observer[] observers; + + VarargConstructor(String whatever, Observer... observers) { + this.whatever = whatever; + this.observers = observers; + } + } + + static class WrapperConstructor { + Set set; + Map map; + Boolean bool; + + WrapperConstructor(Set set, Map map, Boolean bool) { + this.set = set; + this.map = map; + this.bool = bool; + } + } + + static class PrimitiveConstructor { + Set set; + Map map; + boolean bool; + + PrimitiveConstructor(Set set, Map map, boolean bool) { + this.set = set; + this.map = map; + this.bool = bool; + } + } + +} diff --git a/src/test/java/org/mockito/internal/util/reflection/FieldInitializerTest.java b/src/test/java/org/mockito/internal/util/reflection/FieldInitializerTest.java index 8fbe6ee0b2..122b00e052 100644 --- a/src/test/java/org/mockito/internal/util/reflection/FieldInitializerTest.java +++ b/src/test/java/org/mockito/internal/util/reflection/FieldInitializerTest.java @@ -4,21 +4,26 @@ */ package org.mockito.internal.util.reflection; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.BDDMockito.given; -import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.util.HashSet; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.exceptions.base.MockitoException; -import org.mockito.internal.util.reflection.FieldInitializer.ConstructorArgumentResolver; - - +@SuppressWarnings("unchecked") public class FieldInitializerTest { private StaticClass alreadyInstantiated = new StaticClass(); @@ -30,60 +35,72 @@ public class FieldInitializerTest { private AbstractStaticClass abstractType; private Interface interfaceType; private InnerClassType innerClassType; + private EnumType enumType; private AbstractStaticClass instantiatedAbstractType = new ConcreteStaticClass(); private Interface instantiatedInterfaceType = new ConcreteStaticClass(); private InnerClassType instantiatedInnerClassType = new InnerClassType(); + private EnumType initializedEnumType = EnumType.INITIALIZED; @Test public void should_keep_same_instance_if_field_initialized() throws Exception { final StaticClass backupInstance = alreadyInstantiated; - FieldInitializer fieldInitializer = new FieldInitializer(this, field("alreadyInstantiated")); + final ConstructorResolver resolver = mock(ConstructorResolver.class); + + FieldInitializer fieldInitializer = new FieldInitializer(this, field("alreadyInstantiated"), resolver); FieldInitializationReport report = fieldInitializer.initialize(); assertSame(backupInstance, report.fieldInstance()); assertFalse(report.fieldWasInitialized()); - assertFalse(report.fieldWasInitializedUsingContructorArgs()); } @Test public void should_instantiate_field_when_type_has_no_constructor() throws Exception { - FieldInitializer fieldInitializer = new FieldInitializer(this, field("noConstructor")); + final ConstructorResolver resolver = createNoArgsResolverMock(StaticClass.class); + FieldInitializer fieldInitializer = new FieldInitializer(this, field("noConstructor"), resolver); FieldInitializationReport report = fieldInitializer.initialize(); assertNotNull(report.fieldInstance()); assertTrue(report.fieldWasInitialized()); - assertFalse(report.fieldWasInitializedUsingContructorArgs()); } @Test public void should_instantiate_field_with_default_constructor() throws Exception { - FieldInitializer fieldInitializer = new FieldInitializer(this, field("defaultConstructor")); + final ConstructorResolver resolver = createNoArgsResolverMock(StaticClassWithDefaultConstructor.class); + FieldInitializer fieldInitializer = new FieldInitializer(this, field("defaultConstructor"), resolver); FieldInitializationReport report = fieldInitializer.initialize(); assertNotNull(report.fieldInstance()); assertTrue(report.fieldWasInitialized()); - assertFalse(report.fieldWasInitializedUsingContructorArgs()); } @Test public void should_instantiate_field_with_private_default_constructor() throws Exception { - FieldInitializer fieldInitializer = new FieldInitializer(this, field("privateDefaultConstructor")); + final ConstructorResolver resolver = createNoArgsResolverMock(StaticClassWithPrivateDefaultConstructor.class); + FieldInitializer fieldInitializer = new FieldInitializer(this, field("privateDefaultConstructor"), resolver); FieldInitializationReport report = fieldInitializer.initialize(); assertNotNull(report.fieldInstance()); assertTrue(report.fieldWasInitialized()); - assertFalse(report.fieldWasInitializedUsingContructorArgs()); } - @Test(expected = MockitoException.class) - public void should_fail_to_instantiate_field_if_no_default_constructor() throws Exception { - FieldInitializer fieldInitializer = new FieldInitializer(this, field("noDefaultConstructor")); - fieldInitializer.initialize(); + @Test + public void should_not_instantiate_field_if_resolver_says_field_is_not_resolvable() throws Exception { + final ConstructorResolver resolver = mock(ConstructorResolver.class); + given(resolver.isResolvable()).willReturn(false); + + FieldInitializer fieldInitializer = new FieldInitializer(this, field("noDefaultConstructor"), resolver); + FieldInitializationReport report = fieldInitializer.initialize(); + + assertThat(report).isNotNull(); + assertThat(report.fieldIsInitialized()).isFalse(); + assertThat(report.fieldInstance()).isNull(); + assertThat(report.fieldWasInitialized()).isFalse(); } @Test public void should_fail_to_instantiate_field_if_default_constructor_throws_exception() throws Exception { - FieldInitializer fieldInitializer = new FieldInitializer(this, field("throwingExDefaultConstructor")); + final ConstructorResolver resolver = createNoArgsResolverMock(StaticClassThrowingExceptionDefaultConstructor.class); + FieldInitializer fieldInitializer = new FieldInitializer(this, field("throwingExDefaultConstructor"), resolver); try { fieldInitializer.initialize(); fail(); @@ -94,24 +111,54 @@ public void should_fail_to_instantiate_field_if_default_constructor_throws_excep } } + @Test + public void should_fail_to_instantiate_field_if_resolver_throws_expcetion() throws Exception { + final ConstructorResolver resolver = mock(ConstructorResolver.class); + given(resolver.isResolvable()).willThrow(new MockitoException("resolver fails")); + + try { + new FieldInitializer(this, field("noDefaultConstructor"), resolver).initialize(); + fail(); + } catch (MockitoException e) { + assertThat(e.getMessage()).contains("noDefaultConstructor").contains("resolver fails"); + } + } + + @Test + public void should_fail_if_an_argument_instance_type_do_not_match_wanted_type() throws Exception { + final ConstructorResolver resolver = createParameterizedResolverMock(); + given(resolver.resolveArguments()).willReturn(new Object[] {new HashSet()}); + + try { + new FieldInitializer(this, field("noDefaultConstructor"), resolver).initialize(); + fail(); + } catch (MockitoException e) { + assertThat(e.getMessage()).contains("ConstructorResolver").contains("incorrect types"); + } + } + @Test(expected = MockitoException.class) public void should_fail_for_abstract_field() throws Exception { - new FieldInitializer(this, field("abstractType")); + final ConstructorResolver resolver = mock(ConstructorResolver.class); + new FieldInitializer(this, field("abstractType"), resolver); } @Test public void should_not_fail_if_abstract_field_is_instantiated() throws Exception { - new FieldInitializer(this, field("instantiatedAbstractType")); + final ConstructorResolver resolver = mock(ConstructorResolver.class); + new FieldInitializer(this, field("instantiatedAbstractType"), resolver); } @Test(expected = MockitoException.class) public void should_fail_for_interface_field() throws Exception { - new FieldInitializer(this, field("interfaceType")); + final ConstructorResolver resolver = mock(ConstructorResolver.class); + new FieldInitializer(this, field("interfaceType"), resolver); } @Test public void should_not_fail_if_interface_field_is_instantiated() throws Exception { - new FieldInitializer(this, field("instantiatedInterfaceType")); + final ConstructorResolver resolver = mock(ConstructorResolver.class); + new FieldInitializer(this, field("instantiatedInterfaceType"), resolver); } @Test(expected = MockitoException.class) @@ -126,7 +173,8 @@ class TheTestWithLocalType { TheTestWithLocalType testWithLocalType = new TheTestWithLocalType(); // when - new FieldInitializer(testWithLocalType, testWithLocalType.getClass().getDeclaredField("field")); + final ConstructorResolver resolver = mock(ConstructorResolver.class); + new FieldInitializer(testWithLocalType, testWithLocalType.getClass().getDeclaredField("field"), resolver); } @Test @@ -141,24 +189,37 @@ class TheTestWithLocalType { TheTestWithLocalType testWithLocalType = new TheTestWithLocalType(); // when - new FieldInitializer(testWithLocalType, testWithLocalType.getClass().getDeclaredField("field")); + final ConstructorResolver resolver = mock(ConstructorResolver.class); + new FieldInitializer(testWithLocalType, testWithLocalType.getClass().getDeclaredField("field"), resolver); } @Test(expected = MockitoException.class) public void should_fail_for_inner_class_field() throws Exception { - new FieldInitializer(this, field("innerClassType")); + final ConstructorResolver resolver = mock(ConstructorResolver.class); + new FieldInitializer(this, field("innerClassType"), resolver); } @Test public void should_not_fail_if_inner_class_field_is_instantiated() throws Exception { - new FieldInitializer(this, field("instantiatedInnerClassType")); + final ConstructorResolver resolver = mock(ConstructorResolver.class); + new FieldInitializer(this, field("instantiatedInnerClassType"), resolver); + } + + @Test(expected = MockitoException.class) + public void should_fail_for_enum_field() throws Exception { + final ConstructorResolver resolver = mock(ConstructorResolver.class); + new FieldInitializer(this, field("enumType"), resolver); } @Test - public void can_instantiate_class_with_parameterized_constructor() throws Exception { - ConstructorArgumentResolver resolver = given(mock(ConstructorArgumentResolver.class).resolveTypeInstances(any(Class.class))) - .willReturn(new Object[]{null}).getMock(); + public void should_not_fail_if_enum_field_is_instantiated() throws Exception { + final ConstructorResolver resolver = mock(ConstructorResolver.class); + new FieldInitializer(this, field("initializedEnumType"), resolver); + } + @Test + public void can_instantiate_class_with_parameterized_constructor() throws Exception { + final ConstructorResolver resolver = createParameterizedResolverMock(); new FieldInitializer(this, field("noDefaultConstructor"), resolver).initialize(); assertNotNull(noDefaultConstructor); @@ -168,6 +229,23 @@ private Field field(String fieldName) throws NoSuchFieldException { return this.getClass().getDeclaredField(fieldName); } + private ConstructorResolver createNoArgsResolverMock(Class type) throws NoSuchMethodException { + final ConstructorResolver resolver = mock(ConstructorResolver.class); + final Constructor constructor = type.getDeclaredConstructor(); + given(resolver.resolveConstructor()).willReturn(constructor); + given(resolver.isResolvable()).willReturn(true); + return resolver; + } + + private ConstructorResolver createParameterizedResolverMock() throws NoSuchMethodException { + final ConstructorResolver resolver = mock(ConstructorResolver.class); + final Constructor constructor = StaticClassWithoutDefaultConstructor.class.getDeclaredConstructor(String.class); + given(resolver.resolveConstructor()).willReturn(constructor); + given(resolver.isResolvable()).willReturn(true); + given(resolver.resolveArguments()).willReturn(new Object[]{null}); + return resolver; + } + static class StaticClass { } @@ -204,4 +282,8 @@ class InnerClassType { InnerClassType() { } } + enum EnumType { + INITIALIZED + } + } diff --git a/src/test/java/org/mockito/internal/util/reflection/LenientNoArgsConstructorResolverTest.java b/src/test/java/org/mockito/internal/util/reflection/LenientNoArgsConstructorResolverTest.java new file mode 100644 index 0000000000..9d2f97f721 --- /dev/null +++ b/src/test/java/org/mockito/internal/util/reflection/LenientNoArgsConstructorResolverTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.internal.util.reflection.ConstructorResolver.LenientNoArgsConstructorResolver; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class LenientNoArgsConstructorResolverTest extends NoArgsConstructorResolverTest { + + @Override + public void is_resolvable_should_fail_if_no_no_args_constructor_found() throws Exception { + // This test is not relevant for LenientNoArgsConstructorResolver + } + + @Test + public void is_resolvable_should_return_false_if_no_no_args_constructor_found() throws Exception { + final LenientNoArgsConstructorResolver resolver = buildNoArgsConstructorResolver(OneConstructor.class); + + assertThat(resolver.isResolvable()).isFalse(); + } + + @Override + public void is_resolvable_should_fail_with_vararg_constructor() throws Exception { + // This test is not relevant for LenientNoArgsConstructorResolver + } + + @Test + public void is_resolvable_should_return_false_with_vararg_constructor() throws Exception { + final LenientNoArgsConstructorResolver resolver = buildNoArgsConstructorResolver(VarargConstructor.class); + + assertThat(resolver.isResolvable()).isFalse(); + } + + @Override + protected LenientNoArgsConstructorResolver buildNoArgsConstructorResolver(Class type) { + return new LenientNoArgsConstructorResolver(type); + } + +} diff --git a/src/test/java/org/mockito/internal/util/reflection/NoArgsConstructorResolverTest.java b/src/test/java/org/mockito/internal/util/reflection/NoArgsConstructorResolverTest.java new file mode 100644 index 0000000000..7d4a85eda8 --- /dev/null +++ b/src/test/java/org/mockito/internal/util/reflection/NoArgsConstructorResolverTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +import java.lang.reflect.Constructor; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.exceptions.base.MockitoException; +import org.mockito.internal.util.reflection.ConstructorResolver.NoArgsConstructorResolver; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class NoArgsConstructorResolverTest { + + @Test + public void type_should_be_instantiable_if_it_has_no_args_contructor() throws Exception { + final NoArgsConstructorResolver resolver = buildNoArgsConstructorResolver(NoArgConstructor.class); + final Constructor constructor = resolver.resolveConstructor(); + final Object[] arguments = resolver.resolveArguments(); + final Object newInstance = constructor.newInstance(arguments); + + assertThat(newInstance).isNotNull().isInstanceOf(NoArgConstructor.class); + } + + @Test + public void type_should_be_instantiable_if_it_has_default_contructor() throws Exception { + final NoArgsConstructorResolver resolver = buildNoArgsConstructorResolver(DefaultConstructor.class); + final Constructor constructor = resolver.resolveConstructor(); + final Object[] arguments = resolver.resolveArguments(); + final Object newInstance = constructor.newInstance(arguments); + + assertThat(newInstance).isNotNull().isInstanceOf(DefaultConstructor.class); + } + + @Test + public void is_resolvable_should_return_true_if_type_has_no_args_contructor() throws Exception { + final NoArgsConstructorResolver resolver = buildNoArgsConstructorResolver(NoArgConstructor.class); + + assertThat(resolver.isResolvable()).isTrue(); + } + + @Test + public void is_resolvable_should_return_true_if_type_has_default_contructor() throws Exception { + final NoArgsConstructorResolver resolver = buildNoArgsConstructorResolver(DefaultConstructor.class); + + assertThat(resolver.isResolvable()).isTrue(); + } + + @Test + public void is_resolvable_should_fail_if_no_no_args_constructor_found() throws Exception { + try { + buildNoArgsConstructorResolver(OneConstructor.class).isResolvable(); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("no default constructor").contains("OneConstructor"); + } + } + + @Test + public void is_resolvable_should_fail_with_vararg_constructor() throws Exception { + try { + buildNoArgsConstructorResolver(VarargConstructor.class).isResolvable(); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("no default constructor").contains("VarargConstructor"); + } + } + + @Test + public void resolve_constructor_should_return_no_args_constructor() throws Exception { + final NoArgsConstructorResolver resolver = buildNoArgsConstructorResolver(NoArgConstructor.class); + + final Constructor constructor = resolver.resolveConstructor(); + assertThat(constructor).isNotNull(); + assertThat(constructor.getParameterTypes()).isNotNull().isEmpty(); + } + + @Test + public void resolve_constructor_should_return_default_constructor() throws Exception { + final NoArgsConstructorResolver resolver = buildNoArgsConstructorResolver(DefaultConstructor.class); + + final Constructor constructor = resolver.resolveConstructor(); + assertThat(constructor).isNotNull(); + assertThat(constructor.getParameterTypes()).isNotNull().isEmpty(); + } + + @Test + public void resolve_constructor_should_fail_if_no_no_args_constructor_found() throws Exception { + try { + buildNoArgsConstructorResolver(OneConstructor.class).resolveConstructor(); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("no default constructor").contains("OneConstructor"); + } + } + + @Test + public void resolve_constructor_should_fail_with_vararg_constructor() throws Exception { + try { + buildNoArgsConstructorResolver(VarargConstructor.class).resolveConstructor(); + fail(); + } catch (MockitoException me) { + assertThat(me.getMessage()).contains("no default constructor").contains("VarargConstructor"); + } + } + + @Test + public void resolve_arguments_should_return_empty_object_array() throws Exception { + final NoArgsConstructorResolver resolver = buildNoArgsConstructorResolver(NoArgConstructor.class); + + assertThat(resolver.resolveArguments()).isNotNull().isEmpty(); + } + + protected NoArgsConstructorResolver buildNoArgsConstructorResolver(Class type) { + return new NoArgsConstructorResolver(type); + } + + private static class NoArgConstructor { + NoArgConstructor() { + } + } + + public static class DefaultConstructor { + } + + static class OneConstructor { + public OneConstructor(String whatever) { + } + } + + static class VarargConstructor { + VarargConstructor(String... whatever) { + } + } + +} diff --git a/src/test/java/org/mockito/internal/util/reflection/ParameterizedConstructorInstantiatorTest.java b/src/test/java/org/mockito/internal/util/reflection/ParameterizedConstructorInstantiatorTest.java deleted file mode 100644 index 1ffa6cfbf3..0000000000 --- a/src/test/java/org/mockito/internal/util/reflection/ParameterizedConstructorInstantiatorTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2007 Mockito contributors - * This program is made available under the terms of the MIT License. - */ -package org.mockito.internal.util.reflection; - - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Observer; -import java.util.Set; - -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Matchers; -import org.mockito.Mock; -import org.mockito.exceptions.base.MockitoException; -import org.mockito.internal.util.reflection.FieldInitializer.ConstructorArgumentResolver; -import org.mockito.internal.util.reflection.FieldInitializer.ParameterizedConstructorInstantiator; -import org.mockito.junit.MockitoJUnitRunner; - - -@SuppressWarnings("unchecked") -@RunWith(MockitoJUnitRunner.class) -public class ParameterizedConstructorInstantiatorTest { - - private Set whateverForNow; - private OneConstructor withOneConstructor; - private MultipleConstructor withMultipleConstructor; - private NoArgConstructor withNoArgConstructor; - private ThrowingConstructor withThrowingConstructor; - private VarargConstructor withVarargConstructor; - - @After - public void ensure_instances_to_create_are_null() { - withMultipleConstructor = null; - withOneConstructor = null; - withNoArgConstructor = null; - withThrowingConstructor = null; - withVarargConstructor = null; - } - - @Mock private ConstructorArgumentResolver resolver; - - @Test - public void should_be_created_with_an_argument_resolver() throws Exception { - new ParameterizedConstructorInstantiator(this, field("whateverForNow"), resolver); - } - - @Test - public void should_fail_if_no_parameterized_constructor_found___excluding_inner_and_others_kind_of_types() throws Exception { - try { - new ParameterizedConstructorInstantiator(this, field("withNoArgConstructor"), resolver).instantiate(); - fail(); - } catch (MockitoException me) { - assertThat(me.getMessage()).contains("no parameterized constructor").contains("withNoArgConstructor").contains("NoArgConstructor"); - } - } - - @Test - public void should_instantiate_type_if_resolver_provide_matching_types() throws Exception { - Observer observer = mock(Observer.class); - Map map = mock(Map.class); - given(resolver.resolveTypeInstances(Matchers.[]>anyVararg())).willReturn(new Object[]{ observer, map }); - - new ParameterizedConstructorInstantiator(this, field("withMultipleConstructor"), resolver).instantiate(); - - assertNotNull(withMultipleConstructor); - assertNotNull(withMultipleConstructor.observer); - assertNotNull(withMultipleConstructor.map); - } - - @Test - public void should_fail_if_an_argument_instance_type_do_not_match_wanted_type() throws Exception { - Observer observer = mock(Observer.class); - Set wrongArg = mock(Set.class); - given(resolver.resolveTypeInstances(Matchers.[]>anyVararg())).willReturn(new Object[]{ observer, wrongArg }); - - try { - new ParameterizedConstructorInstantiator(this, field("withMultipleConstructor"), resolver).instantiate(); - fail(); - } catch (MockitoException e) { - assertThat(e.getMessage()).contains("argResolver").contains("incorrect types"); - } - } - - @Test - public void should_report_failure_if_constructor_throws_exception() throws Exception { - given(resolver.resolveTypeInstances(Matchers.[]>anyVararg())).willReturn(new Object[]{ null }); - - try { - new ParameterizedConstructorInstantiator(this, field("withThrowingConstructor"), resolver).instantiate(); - fail(); - } catch (MockitoException e) { - assertThat(e.getMessage()).contains("constructor").contains("raised an exception"); - } - } - - @Test - public void should_instantiate_type_with_vararg_constructor() throws Exception { - Observer[] vararg = new Observer[] { }; - given(resolver.resolveTypeInstances(Matchers.[]>anyVararg())).willReturn(new Object[]{ "", vararg}); - - new ParameterizedConstructorInstantiator(this, field("withVarargConstructor"), resolver).instantiate(); - - assertNotNull(withVarargConstructor); - } - - private Field field(String fieldName) throws NoSuchFieldException { - Field field = this.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - return field; - } - - private static class NoArgConstructor { - NoArgConstructor() { } - } - - private static class OneConstructor { - public OneConstructor(Observer observer) { } - } - - private static class ThrowingConstructor { - public ThrowingConstructor(Observer observer) throws IOException { throw new IOException(); } - } - - private static class MultipleConstructor extends OneConstructor { - Observer observer; - Map map; - - public MultipleConstructor(Observer observer) { this(observer, null); } - public MultipleConstructor(Observer observer, Map map) { - super(observer); - this.observer = observer; - this.map = map; - } - } - - private static class VarargConstructor { - VarargConstructor(String whatever, Observer... observers) { } - } -} diff --git a/src/test/java/org/mockito/internal/util/reflection/StrictBiggestConstructorResolverTest.java b/src/test/java/org/mockito/internal/util/reflection/StrictBiggestConstructorResolverTest.java new file mode 100644 index 0000000000..f73d2b9746 --- /dev/null +++ b/src/test/java/org/mockito/internal/util/reflection/StrictBiggestConstructorResolverTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.internal.util.reflection.ConstructorResolver.StrictBiggestConstructorResolver; +import org.mockito.junit.MockitoJUnitRunner; + +@SuppressWarnings("unchecked") +@RunWith(MockitoJUnitRunner.class) +public class StrictBiggestConstructorResolverTest extends BiggestConstructorResolverTest { + + @Override + public void is_resolvable_should_return_true_when_match_is_not_possible_on_given_type() throws Exception { + // This test is not relevant for LenientNoArgsConstructorResolver + } + + @Test + public void is_resolvable_should_return_false_when_match_is_not_possible_on_given_type() throws Exception { + final HashMap hashMap = mock(HashMap.class); + final StrictBiggestConstructorResolver resolver = buildBiggestConstructorResolver(MultipleConstructor.class, hashMap); + + assertThat(resolver.isResolvable()).isFalse(); + } + + @Override + public void is_resolvable_should_return_true_when_types_are_wrappers() throws Exception { + // This test is not relevant for LenientNoArgsConstructorResolver + } + + @Test + public void is_resolvable_should_return_false_when_types_are_wrappers() throws Exception { + final HashSet hashSet = mock(HashSet.class); + final HashMap hashMap = mock(HashMap.class); + final StrictBiggestConstructorResolver resolver = buildBiggestConstructorResolver(WrapperConstructor.class, hashSet, hashMap); + + assertThat(resolver.isResolvable()).isFalse(); + } + + @Override + public void is_resolvable_should_return_true_when_types_are_primitives() throws Exception { + // This test is not relevant for LenientNoArgsConstructorResolver + } + + @Test + public void is_resolvable_should_return_false_when_types_are_primitives() throws Exception { + final HashSet hashSet = mock(HashSet.class); + final HashMap hashMap = mock(HashMap.class); + final StrictBiggestConstructorResolver resolver = buildBiggestConstructorResolver(PrimitiveConstructor.class, hashSet, hashMap); + + assertThat(resolver.isResolvable()).isFalse(); + } + + @Override + protected StrictBiggestConstructorResolver buildBiggestConstructorResolver(Class type, Object... mocks) { + return new StrictBiggestConstructorResolver(type, new HashSet(Arrays.asList(mocks))); + } + +} diff --git a/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineByConstructorTest.java b/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineByConstructorTest.java new file mode 100644 index 0000000000..67dbc5f944 --- /dev/null +++ b/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineByConstructorTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitousage.annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.internal.configuration.InjectingAnnotationEngine; + +public class InjectingAnnotationEngineByConstructorTest { + + public static class MockedDao { + void assertInjection() { + fail("should be mocked"); + } + } + + public static class ByConstructorSpiedService { + final MockedDao mockedDao; + ByConstructorSpiedService(MockedDao mockedDao) { + this.mockedDao = mockedDao; + } + void assertInjection() { + assertNotNull(mockedDao); + mockedDao.assertInjection(); + } + } + + public static class ByConstructorTestedService { + final MockedDao mockedDao; + final ByConstructorSpiedService byConstructorSpiedService; + ByConstructorTestedService(MockedDao mockedDao, ByConstructorSpiedService byConstructorSpiedService) { + this.mockedDao = mockedDao; + this.byConstructorSpiedService = byConstructorSpiedService; + } + void assertInjection() { + assertNotNull(mockedDao); + mockedDao.assertInjection(); + assertNotNull(byConstructorSpiedService); + byConstructorSpiedService.assertInjection(); + } + } + + @InjectMocks + private ByConstructorTestedService byConstructorTestedService; + + @InjectMocks + @Spy + private ByConstructorSpiedService byConstructorSpiedService; + + @Mock + private MockedDao mockedDao; + + @Test + public void should_inject_mocks_and_spies_by_constructor() { + new InjectingAnnotationEngine().process(getClass(), this); + assertNotNull(byConstructorTestedService); + byConstructorTestedService.assertInjection(); + } + + @Test + public void injected_mocks_and_spies_should_be_of_same_instance() { + new InjectingAnnotationEngine().process(getClass(), this); + assertNotNull(byConstructorTestedService); + byConstructorTestedService.assertInjection(); + assertEquals(byConstructorSpiedService, byConstructorTestedService.byConstructorSpiedService); + assertEquals(mockedDao, byConstructorTestedService.mockedDao); + assertEquals(mockedDao, byConstructorTestedService.byConstructorSpiedService.mockedDao); + } + +} diff --git a/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineByPropertyTest.java b/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineByPropertyTest.java new file mode 100644 index 0000000000..3b17585f83 --- /dev/null +++ b/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineByPropertyTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitousage.annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.internal.configuration.InjectingAnnotationEngine; + +public class InjectingAnnotationEngineByPropertyTest { + + public static class MockedDao { + void assertInjection() { + fail("should be mocked"); + } + } + + public static class ByPropertySpiedService { + MockedDao mockedDao; + void assertInjection() { + assertNotNull(mockedDao); + mockedDao.assertInjection(); + } + } + + public static class ByPropertyTestedService { + MockedDao mockedDao; + ByPropertySpiedService byPropertySpiedService; + void assertInjection() { + assertNotNull(mockedDao); + mockedDao.assertInjection(); + assertNotNull(byPropertySpiedService); + byPropertySpiedService.assertInjection(); + } + } + + @InjectMocks + private ByPropertyTestedService byPropertyTestedService; + + @InjectMocks + @Spy + private ByPropertySpiedService byPropertySpiedService; + + @Mock + private MockedDao mockedDao; + + @Test + public void should_inject_mocks_and_spies_by_property() { + new InjectingAnnotationEngine().process(getClass(), this); + assertNotNull(byPropertyTestedService); + byPropertyTestedService.assertInjection(); + } + + @Test + public void injected_mocks_and_spies_should_be_of_same_instance() { + new InjectingAnnotationEngine().process(getClass(), this); + assertNotNull(byPropertyTestedService); + byPropertyTestedService.assertInjection(); + assertEquals(byPropertySpiedService, byPropertyTestedService.byPropertySpiedService); + assertEquals(mockedDao, byPropertyTestedService.mockedDao); + assertEquals(mockedDao, byPropertyTestedService.byPropertySpiedService.mockedDao); + } + +} diff --git a/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineCircularInjectionByConstructorTest.java b/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineCircularInjectionByConstructorTest.java new file mode 100644 index 0000000000..d1edede23f --- /dev/null +++ b/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineCircularInjectionByConstructorTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitousage.annotation; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.mockito.internal.configuration.InjectingAnnotationEngine; + +public class InjectingAnnotationEngineCircularInjectionByConstructorTest { + + public static class TestedService { + final SpiedService1 service1; + final SpiedService2 service2; + public TestedService(SpiedService1 service1, SpiedService2 service2) { + this.service1 = service1; + this.service2 = service2; + } + } + + public static class SpiedService1 { + final SpiedService2 service2; + public SpiedService1(SpiedService2 service2) { + this.service2 = service2; + } + } + + public static class SpiedService2 { + final SpiedService1 service1; + public SpiedService2(SpiedService1 service) { + this.service1 = service; + } + } + + @InjectMocks + private TestedService testedService; + + @InjectMocks + @Spy + private SpiedService1 spiedService1; + + @InjectMocks + @Spy + private SpiedService2 spiedService2; + + @Test + public void should_fail_to_inject_circular_dependencies_by_constructor() { + new InjectingAnnotationEngine().process(getClass(), this); + Assert.assertNotNull(testedService); + Assert.assertNull(testedService.service1); + Assert.assertNull(testedService.service2); + Assert.assertNotNull(spiedService1); + Assert.assertNull(spiedService1.service2); + Assert.assertNotNull(spiedService2); + Assert.assertNull(spiedService2.service1); + } + +} diff --git a/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineCircularInjectionByPropertyTest.java b/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineCircularInjectionByPropertyTest.java new file mode 100644 index 0000000000..917adc7da9 --- /dev/null +++ b/src/test/java/org/mockitousage/annotation/InjectingAnnotationEngineCircularInjectionByPropertyTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitousage.annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.mockito.internal.configuration.InjectingAnnotationEngine; + +public class InjectingAnnotationEngineCircularInjectionByPropertyTest { + + public static class SpiedService1 { + SpiedService2 spiedService2; + void assertInjection() { + assertNotNull(spiedService2); + } + } + + public static class SpiedService2 { + SpiedService1 spiedService1; + void assertInjection() { + assertNotNull(spiedService1); + } + } + + public static class TestedService { + SpiedService1 spiedService1; + SpiedService2 spiedService2; + void assertInjection() { + assertNotNull(spiedService1); + assertNotNull(spiedService2); + spiedService1.assertInjection(); + spiedService2.assertInjection(); + } + } + + @InjectMocks + private TestedService testedService; + + @InjectMocks + @Spy + private SpiedService1 spiedService1; + + @InjectMocks + @Spy + private SpiedService2 spiedService2; + + @Test + public void should_inject_mocks_and_spies_by_property() { + new InjectingAnnotationEngine().process(getClass(), this); + assertNotNull(testedService); + testedService.assertInjection(); + } + + @Test + public void injected_mocks_and_spies_should_be_of_same_instance() { + new InjectingAnnotationEngine().process(getClass(), this); + assertNotNull(testedService); + testedService.assertInjection(); + assertEquals(spiedService1, testedService.spiedService1); + assertEquals(spiedService1, testedService.spiedService2.spiedService1); + assertEquals(spiedService2, testedService.spiedService2); + assertEquals(spiedService2, testedService.spiedService1.spiedService2); + } + +}