diff --git a/src/main/java/com/github/nylle/javafixture/InstanceFactory.java b/src/main/java/com/github/nylle/javafixture/InstanceFactory.java index 2740451..9386395 100644 --- a/src/main/java/com/github/nylle/javafixture/InstanceFactory.java +++ b/src/main/java/com/github/nylle/javafixture/InstanceFactory.java @@ -57,16 +57,16 @@ public InstanceFactory(SpecimenFactory specimenFactory) { this.random = new PseudoRandom(); } - public T construct(final SpecimenType type, CustomizationContext customizationContext) { + public T construct(SpecimenType type, CustomizationContext customizationContext) { return random.shuffled(type.getDeclaredConstructors()) .stream() .filter(x -> Modifier.isPublic(x.getModifiers())) .findFirst() .map(x -> construct(type, x, customizationContext)) - .orElseGet(() -> manufacture(type, customizationContext)); + .orElseGet(() -> manufacture(type, customizationContext, new SpecimenException("No public constructor found"))); } - public T manufacture(final SpecimenType type, CustomizationContext customizationContext) { + public T manufacture(SpecimenType type, CustomizationContext customizationContext, Throwable throwable) { return random.shuffled(type.getFactoryMethods()) .stream() .filter(method -> Modifier.isPublic(method.getModifiers())) @@ -74,10 +74,10 @@ public T manufacture(final SpecimenType type, CustomizationContext custom .map(x -> manufactureOrNull(x, type, customizationContext)) .filter(x -> x != null) .findFirst() - .orElseThrow(() -> new SpecimenException(format("Cannot create instance of %s", type.asClass()))); + .orElseThrow(() -> new SpecimenException(format("Cannot create instance of %s: no factory-method found", type.asClass()), throwable)); } - public T instantiate(final SpecimenType type) { + public T instantiate(SpecimenType type) { try { return type.asClass().getDeclaredConstructor().newInstance(); } catch (Exception e) { @@ -111,8 +111,8 @@ private T construct(final SpecimenType type, final Constructor constru return (T) constructor.newInstance(stream(constructor.getParameters()) .map(p -> createParameter(p, customizationContext)) .toArray()); - } catch (Exception e) { - return manufacture(type, customizationContext); + } catch (Exception ex) { + return manufacture(type, customizationContext, ex); } } @@ -149,8 +149,8 @@ private T createProxyForAbstract(final SpecimenType type, final Map[0], new Object[0], new ProxyInvocationHandler(specimenFactory, specimens)); - } catch (Exception e) { - throw new SpecimenException(format("Unable to create instance of abstract class %s: %s", type.asClass(), e.getMessage()), e); + } catch (Exception ex) { + throw new SpecimenException(format("Unable to create instance of abstract %s: %s", type.asClass(), ex.getMessage()), ex); } } diff --git a/src/main/java/com/github/nylle/javafixture/specimen/AbstractSpecimen.java b/src/main/java/com/github/nylle/javafixture/specimen/AbstractSpecimen.java index 6d1cdc7..eb83c19 100644 --- a/src/main/java/com/github/nylle/javafixture/specimen/AbstractSpecimen.java +++ b/src/main/java/com/github/nylle/javafixture/specimen/AbstractSpecimen.java @@ -62,8 +62,8 @@ public T create(final CustomizationContext customizationContext, Annotation[] an field.getGenericType().getTypeName(), specimenFactory.build(SpecimenType.fromClass(field.getGenericType()))).create(new CustomizationContext(List.of(), Map.of(), false), field.getAnnotations())))); return context.remove(type); - } catch(SpecimenException ignored) { - return instanceFactory.manufacture(type, customizationContext); + } catch(SpecimenException ex) { + return instanceFactory.manufacture(type, customizationContext, ex); } } } diff --git a/src/test/java/com/github/nylle/javafixture/InstanceFactoryTest.java b/src/test/java/com/github/nylle/javafixture/InstanceFactoryTest.java index ffa1239..4c7bab0 100644 --- a/src/test/java/com/github/nylle/javafixture/InstanceFactoryTest.java +++ b/src/test/java/com/github/nylle/javafixture/InstanceFactoryTest.java @@ -11,6 +11,7 @@ import com.github.nylle.javafixture.testobjects.factorymethod.GenericClassWithFactoryMethodWithoutArgument; import com.github.nylle.javafixture.testobjects.factorymethod.TestObjectWithNonPublicFactoryMethods; import com.github.nylle.javafixture.testobjects.interfaces.InterfaceWithDefaultMethod; +import com.github.nylle.javafixture.testobjects.withconstructor.ConstructorExceptionAndNoFactoryMethod; import com.github.nylle.javafixture.testobjects.withconstructor.TestObjectWithConstructedField; import com.github.nylle.javafixture.testobjects.withconstructor.TestObjectWithGenericConstructor; import com.github.nylle.javafixture.testobjects.withconstructor.TestObjectWithPrivateConstructor; @@ -18,6 +19,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import java.lang.reflect.InvocationTargetException; import java.nio.charset.Charset; import java.util.ArrayDeque; import java.util.ArrayList; @@ -97,7 +99,9 @@ void canOnlyUsePublicConstructor() { assertThatExceptionOfType(SpecimenException.class) .isThrownBy(() -> sut.construct(fromClass(TestObjectWithPrivateConstructor.class), new CustomizationContext(List.of(), Map.of(), false))) .withMessageContaining("Cannot create instance of class") - .withNoCause(); + .havingCause() + .isInstanceOf(SpecimenException.class) + .withMessage("No public constructor found"); } @Test @@ -120,6 +124,17 @@ void fallbackToFactoryMethodWhenConstructorThrowsException() { assertThat(result.getValue()).isNotNull(); } + @Test + @DisplayName("will fallback to factory method and pass exceptions on") + void passExceptionToFallbackWhenConstructorThrows() { + var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); + + assertThatExceptionOfType(SpecimenException.class) + .isThrownBy(() -> sut.construct(new SpecimenType() {}, new CustomizationContext(List.of(), Map.of(), false))) + .withMessageContaining("Cannot create instance of class") + .withCauseInstanceOf(InvocationTargetException.class); + } + @Test @DisplayName("arguments can be customized") void argumentsCanBeCustomized() { @@ -178,7 +193,7 @@ class FactoryMethods { void canCreateInstanceFromAbstractClassUsingFactoryMethod() { var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); - var actual = sut.manufacture(new SpecimenType() {}, noContext()); + var actual = sut.manufacture(new SpecimenType() {}, noContext(), null); assertThat(actual).isInstanceOf(Charset.class); } @@ -189,17 +204,30 @@ void canOnlyUsePublicFactoryMethods() { var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); assertThatExceptionOfType(SpecimenException.class) - .isThrownBy(() -> sut.manufacture(fromClass(TestObjectWithNonPublicFactoryMethods.class), noContext())) + .isThrownBy(() -> sut.manufacture(fromClass(TestObjectWithNonPublicFactoryMethods.class), noContext(), null)) .withMessageContaining("Cannot create instance of class") .withNoCause(); } + @Test + @DisplayName("includes provided throwable as cause") + void takesThrowableAsCause() { + var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); + + var throwable = new RuntimeException("expected for test"); + + assertThatExceptionOfType(SpecimenException.class) + .isThrownBy(() -> sut.manufacture(fromClass(TestObjectWithNonPublicFactoryMethods.class), noContext(), throwable)) + .withMessageContaining("Cannot create instance of class") + .withCause(throwable); + } + @Test @DisplayName("method arguments are used") void factoryMethodWithArgument() { var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); - FactoryMethodWithArgument result = sut.manufacture(fromClass(FactoryMethodWithArgument.class), noContext()); + FactoryMethodWithArgument result = sut.manufacture(fromClass(FactoryMethodWithArgument.class), noContext(), null); assertThat(result.getValue()).isNotNull(); } @@ -209,7 +237,7 @@ void factoryMethodWithArgument() { void shouldFilter() { var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); - FactoryMethodWithItselfAsArgument result = sut.manufacture(fromClass(FactoryMethodWithItselfAsArgument.class), noContext()); + FactoryMethodWithItselfAsArgument result = sut.manufacture(fromClass(FactoryMethodWithItselfAsArgument.class), noContext(), null); assertThat(result.getValue()).isNull(); } @@ -220,7 +248,7 @@ void shouldFailWithoutValidFactoryMethod() { var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); assertThatExceptionOfType(SpecimenException.class) - .isThrownBy(() -> sut.manufacture(fromClass(FactoryMethodWithOnlyItselfAsArgument.class), noContext())); + .isThrownBy(() -> sut.manufacture(fromClass(FactoryMethodWithOnlyItselfAsArgument.class), noContext(), null)); } @@ -229,7 +257,7 @@ void shouldFailWithoutValidFactoryMethod() { void createOptional() { var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); - var result = sut.manufacture(new SpecimenType>() {}, noContext()); + var result = sut.manufacture(new SpecimenType>() {}, noContext(), null); assertThat(result).isInstanceOf(Optional.class); assertThat(result.orElse("optional may be empty")).isInstanceOf(String.class); @@ -240,7 +268,7 @@ void createOptional() { void factoryMethodWithoutArgument() { var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); - FactoryMethodWithoutArgument result = sut.manufacture(fromClass(FactoryMethodWithoutArgument.class), noContext()); + FactoryMethodWithoutArgument result = sut.manufacture(fromClass(FactoryMethodWithoutArgument.class), noContext(), null); assertThat(result.getValue()).isEqualTo(42); } @@ -250,7 +278,7 @@ void factoryMethodWithoutArgument() { void factoryMethodWithGenericArgument() { var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); - var result = sut.manufacture(new SpecimenType>() {}, noContext()); + var result = sut.manufacture(new SpecimenType>() {}, noContext(), null); assertThat(result.getValue()).isNotNull(); } @@ -260,7 +288,7 @@ void factoryMethodWithGenericArgument() { void genericNoArgumentFactoryMethod() { var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); - var result = sut.manufacture(new SpecimenType>() {}, noContext()); + var result = sut.manufacture(new SpecimenType>() {}, noContext(), null); assertThat(result).isNotNull(); assertThat(result.getValue()).isEqualTo(42); @@ -461,7 +489,7 @@ class ProxyTest { void callsDefaultMethods() { var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); - var actual = (InterfaceWithDefaultMethod) sut.proxy(new SpecimenType() {}, new HashMap>()); + var actual = (InterfaceWithDefaultMethod) sut.proxy(new SpecimenType() {}, new HashMap<>()); assertThat(actual.getTestObject()).isNotNull(); assertThat(actual.getDefaultInt()).isEqualTo(42); @@ -472,7 +500,7 @@ void callsDefaultMethods() { void fromAbstractClass() { var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure()))); - var actual = (AbstractClassWithConcreteMethod) sut.proxy(new SpecimenType() {}, new HashMap>()); + var actual = (AbstractClassWithConcreteMethod) sut.proxy(new SpecimenType() {}, new HashMap<>()); assertThat(actual.getTestObject()).isNotNull(); assertThat(actual.getDefaultInt()).isEqualTo(42); diff --git a/src/test/java/com/github/nylle/javafixture/specimen/AbstractSpecimenTest.java b/src/test/java/com/github/nylle/javafixture/specimen/AbstractSpecimenTest.java index 405e750..5625177 100644 --- a/src/test/java/com/github/nylle/javafixture/specimen/AbstractSpecimenTest.java +++ b/src/test/java/com/github/nylle/javafixture/specimen/AbstractSpecimenTest.java @@ -2,10 +2,12 @@ import com.github.nylle.javafixture.Configuration; import com.github.nylle.javafixture.Context; +import com.github.nylle.javafixture.SpecimenException; import com.github.nylle.javafixture.SpecimenFactory; import com.github.nylle.javafixture.SpecimenType; import com.github.nylle.javafixture.testobjects.TestAbstractClass; import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithConcreteMethod; +import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithConstructorException; import com.github.nylle.javafixture.testobjects.interfaces.InterfaceWithoutImplementation; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -14,6 +16,7 @@ import java.io.IOException; import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -123,6 +126,24 @@ void creatingAbstractClassWithoutConstructorFallsBackToManufacturing() { assertThat(context.isCached(specimenType)).isFalse(); } + @Test + void exceptionsArePassedOnToManufacturingFallback() { + var specimenType = SpecimenType.fromClass(AbstractClassWithConstructorException.class); + var sut = new AbstractSpecimen<>(specimenType, context, specimenFactory); + + assertThatExceptionOfType(SpecimenException.class) + .isThrownBy(() -> sut.create(noContext(), new Annotation[0])) + .withMessage("Cannot create instance of class com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithConstructorException: no factory-method found") + .havingCause() + .isInstanceOf(SpecimenException.class) + .withMessage("Unable to create instance of abstract class com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithConstructorException: null") + .havingCause() + .isInstanceOf(InvocationTargetException.class) + .havingCause() + .isInstanceOf(IllegalArgumentException.class) + .withMessage("expected for tests"); + } + @Test void resultIsNotCached() { diff --git a/src/test/java/com/github/nylle/javafixture/testobjects/abstractclasses/AbstractClassWithConstructorException.java b/src/test/java/com/github/nylle/javafixture/testobjects/abstractclasses/AbstractClassWithConstructorException.java new file mode 100644 index 0000000..5564676 --- /dev/null +++ b/src/test/java/com/github/nylle/javafixture/testobjects/abstractclasses/AbstractClassWithConstructorException.java @@ -0,0 +1,7 @@ +package com.github.nylle.javafixture.testobjects.abstractclasses; + +public abstract class AbstractClassWithConstructorException { + public AbstractClassWithConstructorException() { + throw new IllegalArgumentException("expected for tests"); + } +} diff --git a/src/test/java/com/github/nylle/javafixture/testobjects/withconstructor/ConstructorExceptionAndNoFactoryMethod.java b/src/test/java/com/github/nylle/javafixture/testobjects/withconstructor/ConstructorExceptionAndNoFactoryMethod.java new file mode 100644 index 0000000..9ab168a --- /dev/null +++ b/src/test/java/com/github/nylle/javafixture/testobjects/withconstructor/ConstructorExceptionAndNoFactoryMethod.java @@ -0,0 +1,7 @@ +package com.github.nylle.javafixture.testobjects.withconstructor; + +public class ConstructorExceptionAndNoFactoryMethod { + public ConstructorExceptionAndNoFactoryMethod() { + throw new IllegalArgumentException("expected for tests"); + } +}