Skip to content

Commit

Permalink
feat: pass exceptions on to manufacture
Browse files Browse the repository at this point in the history
Refs: #106
  • Loading branch information
Nylle committed Aug 22, 2024
1 parent 9eebb6d commit 97f6111
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 23 deletions.
18 changes: 9 additions & 9 deletions src/main/java/com/github/nylle/javafixture/InstanceFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,27 +57,27 @@ public InstanceFactory(SpecimenFactory specimenFactory) {
this.random = new PseudoRandom();
}

public <T> T construct(final SpecimenType<T> type, CustomizationContext customizationContext) {
public <T> T construct(SpecimenType<T> 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> T manufacture(final SpecimenType<T> type, CustomizationContext customizationContext) {
public <T> T manufacture(SpecimenType<T> type, CustomizationContext customizationContext, Throwable throwable) {
return random.shuffled(type.getFactoryMethods())
.stream()
.filter(method -> Modifier.isPublic(method.getModifiers()))
.filter(method -> !hasSpecimenTypeAsParameter(method, type))
.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> T instantiate(final SpecimenType<T> type) {
public <T> T instantiate(SpecimenType<T> type) {
try {
return type.asClass().getDeclaredConstructor().newInstance();
} catch (Exception e) {
Expand Down Expand Up @@ -111,8 +111,8 @@ private <T> T construct(final SpecimenType<T> 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);
}
}

Expand Down Expand Up @@ -149,8 +149,8 @@ private <T> T createProxyForAbstract(final SpecimenType<T> type, final Map<Strin
var factory = new ProxyFactory();
factory.setSuperclass(type.asClass());
return (T) factory.create(new Class<?>[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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
52 changes: 40 additions & 12 deletions src/test/java/com/github/nylle/javafixture/InstanceFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
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;
import org.junit.jupiter.api.DisplayName;
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;
Expand Down Expand Up @@ -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
Expand All @@ -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<ConstructorExceptionAndNoFactoryMethod>() {}, new CustomizationContext(List.of(), Map.of(), false)))
.withMessageContaining("Cannot create instance of class")
.withCauseInstanceOf(InvocationTargetException.class);
}

@Test
@DisplayName("arguments can be customized")
void argumentsCanBeCustomized() {
Expand Down Expand Up @@ -178,7 +193,7 @@ class FactoryMethods {
void canCreateInstanceFromAbstractClassUsingFactoryMethod() {
var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure())));

var actual = sut.manufacture(new SpecimenType<Charset>() {}, noContext());
var actual = sut.manufacture(new SpecimenType<Charset>() {}, noContext(), null);

assertThat(actual).isInstanceOf(Charset.class);
}
Expand All @@ -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();
}
Expand All @@ -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();
}
Expand All @@ -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));

}

Expand All @@ -229,7 +257,7 @@ void shouldFailWithoutValidFactoryMethod() {
void createOptional() {
var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure())));

var result = sut.manufacture(new SpecimenType<Optional<String>>() {}, noContext());
var result = sut.manufacture(new SpecimenType<Optional<String>>() {}, noContext(), null);

assertThat(result).isInstanceOf(Optional.class);
assertThat(result.orElse("optional may be empty")).isInstanceOf(String.class);
Expand All @@ -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);
}
Expand All @@ -250,7 +278,7 @@ void factoryMethodWithoutArgument() {
void factoryMethodWithGenericArgument() {
var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure())));

var result = sut.manufacture(new SpecimenType<FactoryMethodWithGenericArgument<Integer>>() {}, noContext());
var result = sut.manufacture(new SpecimenType<FactoryMethodWithGenericArgument<Integer>>() {}, noContext(), null);

assertThat(result.getValue()).isNotNull();
}
Expand All @@ -260,7 +288,7 @@ void factoryMethodWithGenericArgument() {
void genericNoArgumentFactoryMethod() {
var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure())));

var result = sut.manufacture(new SpecimenType<GenericClassWithFactoryMethodWithoutArgument<Integer>>() {}, noContext());
var result = sut.manufacture(new SpecimenType<GenericClassWithFactoryMethodWithoutArgument<Integer>>() {}, noContext(), null);

assertThat(result).isNotNull();
assertThat(result.getValue()).isEqualTo(42);
Expand Down Expand Up @@ -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<InterfaceWithDefaultMethod>() {}, new HashMap<String, ISpecimen<?>>());
var actual = (InterfaceWithDefaultMethod) sut.proxy(new SpecimenType<InterfaceWithDefaultMethod>() {}, new HashMap<>());

assertThat(actual.getTestObject()).isNotNull();
assertThat(actual.getDefaultInt()).isEqualTo(42);
Expand All @@ -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<AbstractClassWithConcreteMethod>() {}, new HashMap<String, ISpecimen<?>>());
var actual = (AbstractClassWithConcreteMethod) sut.proxy(new SpecimenType<AbstractClassWithConcreteMethod>() {}, new HashMap<>());

assertThat(actual.getTestObject()).isNotNull();
assertThat(actual.getDefaultInt()).isEqualTo(42);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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() {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

public abstract class AbstractClassWithConstructorException {
public AbstractClassWithConstructorException() {
throw new IllegalArgumentException("expected for tests");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.github.nylle.javafixture.testobjects.withconstructor;

public class ConstructorExceptionAndNoFactoryMethod {
public ConstructorExceptionAndNoFactoryMethod() {
throw new IllegalArgumentException("expected for tests");
}
}

0 comments on commit 97f6111

Please sign in to comment.