Skip to content

Commit b9421db

Browse files
committed
feat: introduce constructor instantiator
1 parent f0ae1ac commit b9421db

File tree

4 files changed

+226
-0
lines changed

4 files changed

+226
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.github.nylle.javafixture.instantiation;
2+
3+
import com.github.nylle.javafixture.CustomizationContext;
4+
import com.github.nylle.javafixture.SpecimenFactory;
5+
import com.github.nylle.javafixture.SpecimenType;
6+
7+
import java.lang.annotation.Annotation;
8+
import java.lang.reflect.Parameter;
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
import static java.util.Arrays.stream;
13+
14+
public class Constructor<T> implements Instantiator<T> {
15+
16+
private final java.lang.reflect.Constructor<T> constructor;
17+
18+
private Constructor(java.lang.reflect.Constructor<T> constructor) {
19+
this.constructor = constructor;
20+
}
21+
22+
public static <T> Constructor<T> create(java.lang.reflect.Constructor<T> constructor) {
23+
return new Constructor<>(constructor);
24+
}
25+
26+
public T invoke(SpecimenFactory specimenFactory, CustomizationContext customizationContext) {
27+
try {
28+
return constructor.newInstance(stream(constructor.getParameters())
29+
.map(p -> createParameter(p, specimenFactory, customizationContext))
30+
.toArray());
31+
} catch(Exception ex) {
32+
return null;
33+
}
34+
}
35+
36+
private static Object createParameter(Parameter parameter, SpecimenFactory specimenFactory, CustomizationContext customizationContext) {
37+
if (customizationContext.getIgnoredFields().contains(parameter.getName())) {
38+
return Primitive.defaultValue(parameter.getType());
39+
}
40+
if (customizationContext.getCustomFields().containsKey(parameter.getName())) {
41+
return customizationContext.getCustomFields().get(parameter.getName());
42+
}
43+
return specimenFactory
44+
.build(SpecimenType.fromClass(parameter.getParameterizedType()))
45+
.create(new CustomizationContext(List.of(), Map.of(), customizationContext.useRandomConstructor()), new Annotation[0]);
46+
}
47+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.github.nylle.javafixture.instantiation;
2+
3+
import java.util.Map;
4+
5+
public class Primitive {
6+
private static final Map<Class<?>, Object> primitiveDefaults = Map.of(
7+
Boolean.TYPE, false,
8+
Character.TYPE, '\0',
9+
Byte.TYPE, (byte) 0,
10+
Short.TYPE, 0,
11+
Integer.TYPE, 0,
12+
Long.TYPE, 0L,
13+
Float.TYPE, 0.0f,
14+
Double.TYPE, 0.0d
15+
);
16+
17+
private Primitive() {
18+
}
19+
20+
public static Object defaultValue(Class<?> type) {
21+
return primitiveDefaults.get(type);
22+
}
23+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.github.nylle.javafixture.instantiation;
2+
3+
import com.github.nylle.javafixture.Configuration;
4+
import com.github.nylle.javafixture.Context;
5+
import com.github.nylle.javafixture.CustomizationContext;
6+
import com.github.nylle.javafixture.SpecimenFactory;
7+
import com.github.nylle.javafixture.testobjects.TestObject;
8+
import com.github.nylle.javafixture.testobjects.withconstructor.TestObjectWithConstructedField;
9+
import com.github.nylle.javafixture.testobjects.withconstructor.TestObjectWithGenericConstructor;
10+
import org.junit.jupiter.api.DisplayName;
11+
import org.junit.jupiter.api.Test;
12+
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.Optional;
16+
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
class ConstructorTest {
20+
21+
@Test
22+
@DisplayName("returns instance")
23+
void canCreateInstanceFromConstructor() throws NoSuchMethodException {
24+
var sut = Constructor.create(TestObjectWithGenericConstructor.class.getConstructor(String.class, Optional.class));
25+
26+
var actual = sut.invoke(new SpecimenFactory(new Context(Configuration.configure())), new CustomizationContext(List.of(), Map.of(), false));
27+
28+
assertThat(actual).isInstanceOf(TestObjectWithGenericConstructor.class);
29+
assertThat(actual.getValue()).isInstanceOf(String.class);
30+
assertThat(actual.getInteger()).isInstanceOf(Optional.class);
31+
}
32+
33+
@Test
34+
@DisplayName("fields not set by constructor are null")
35+
void fieldsNotSetByConstructorAreNull() throws NoSuchMethodException {
36+
var sut = Constructor.create(TestObjectWithGenericConstructor.class.getConstructor(String.class, Optional.class));
37+
38+
var actual = sut.invoke(new SpecimenFactory(new Context(Configuration.configure())), new CustomizationContext(List.of(), Map.of(), false));
39+
40+
assertThat(actual).isInstanceOf(TestObjectWithGenericConstructor.class);
41+
assertThat(actual.getPrivateField()).isNull();
42+
}
43+
44+
@Test
45+
@DisplayName("arguments can be customized")
46+
void argumentsCanBeCustomized() throws NoSuchMethodException {
47+
var sut = Constructor.create(TestObject.class.getConstructor(String.class, List.class, Map.class));
48+
49+
// use arg0, because .class files do not store formal parameter names by default
50+
var actual = sut.invoke(new SpecimenFactory(new Context(Configuration.configure())), new CustomizationContext(List.of(), Map.of("arg0", "customized"), true));
51+
52+
assertThat(actual.getValue()).isEqualTo("customized");
53+
}
54+
55+
@Test
56+
@DisplayName("using constructor is used for all instances")
57+
void usingConstructorIsRecursive() throws NoSuchMethodException {
58+
var sut = Constructor.create(TestObjectWithConstructedField.class.getConstructor(int.class, TestObjectWithGenericConstructor.class));
59+
60+
var actual = sut.invoke(new SpecimenFactory(new Context(Configuration.configure())), new CustomizationContext(List.of(), Map.of(), true));
61+
62+
assertThat(actual).isInstanceOf(TestObjectWithConstructedField.class);
63+
assertThat(actual.getTestObjectWithGenericConstructor().getPrivateField()).isNull();
64+
assertThat(actual.getNotSetByConstructor()).isNull();
65+
}
66+
67+
@Test
68+
@DisplayName("customized arguments are only used for the top level object (no nested objects)")
69+
void constructorArgumentsAreUsedOnce() throws NoSuchMethodException {
70+
var sut = Constructor.create(TestObjectWithConstructedField.class.getConstructor(int.class, TestObjectWithGenericConstructor.class));
71+
72+
// use arg0, because .class files do not store formal parameter names by default
73+
var actual = sut.invoke(new SpecimenFactory(new Context(Configuration.configure())), new CustomizationContext(List.of(), Map.of("arg0", 2), true));
74+
75+
assertThat(actual.getSetByConstructor()).isEqualTo(2);
76+
}
77+
78+
@Test
79+
@DisplayName("customized arguments are used for exclusion, too")
80+
void ignoredConstructorArgsAreRespected() throws NoSuchMethodException {
81+
var sut = Constructor.create(TestObjectWithConstructedField.class.getConstructor(int.class, TestObjectWithGenericConstructor.class));
82+
83+
// use arg0, because .class files do not store formal parameter names by default
84+
var actual = sut.invoke(new SpecimenFactory(new Context(Configuration.configure())), new CustomizationContext(List.of("arg0"), Map.of(), true));
85+
86+
assertThat(actual.getSetByConstructor()).isEqualTo(0);
87+
}
88+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.github.nylle.javafixture.instantiation;
2+
3+
import com.github.nylle.javafixture.annotations.testcases.TestCase;
4+
import com.github.nylle.javafixture.annotations.testcases.TestWithCases;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
class PrimitiveTest {
10+
11+
@Test
12+
void returnsDefaultBoolean() {
13+
assertThat(Primitive.defaultValue(boolean.class)).isEqualTo(false);
14+
}
15+
16+
@Test
17+
void returnsDefaultCharacter() {
18+
assertThat(Primitive.defaultValue(char.class)).isEqualTo('\0');
19+
}
20+
21+
@Test
22+
void returnsDefaultByte() {
23+
assertThat(Primitive.defaultValue(byte.class)).isEqualTo((byte) 0);
24+
}
25+
26+
@TestWithCases
27+
@TestCase(class1 = short.class)
28+
@TestCase(class1 = int.class)
29+
void returnsDefaultInteger(Class<?> type) {
30+
assertThat(Primitive.defaultValue(type)).isEqualTo(0);
31+
}
32+
33+
@Test
34+
void returnsDefaultLong() {
35+
assertThat(Primitive.defaultValue(long.class)).isEqualTo(0L);
36+
}
37+
38+
@Test
39+
void returnsDefaultFloat() {
40+
assertThat(Primitive.defaultValue(float.class)).isEqualTo(0.0f);
41+
}
42+
43+
@Test
44+
void returnsDefaultDouble() {
45+
assertThat(Primitive.defaultValue(double.class)).isEqualTo(0.0d);
46+
}
47+
48+
@TestWithCases
49+
@TestCase(class1 = Boolean.class, bool2 = true)
50+
@TestCase(class1 = Character.class, bool2 = true)
51+
@TestCase(class1 = Byte.class, bool2 = true)
52+
@TestCase(class1 = Short.class, bool2 = true)
53+
@TestCase(class1 = Integer.class, bool2 = true)
54+
@TestCase(class1 = Long.class, bool2 = true)
55+
@TestCase(class1 = Float.class, bool2 = true)
56+
@TestCase(class1 = Double.class, bool2 = true)
57+
@TestCase(class1 = boolean.class, bool2 = false)
58+
@TestCase(class1 = char.class, bool2 = false)
59+
@TestCase(class1 = byte.class, bool2 = false)
60+
@TestCase(class1 = short.class, bool2 = false)
61+
@TestCase(class1 = int.class, bool2 = false)
62+
@TestCase(class1 = long.class, bool2 = false)
63+
@TestCase(class1 = float.class, bool2 = false)
64+
@TestCase(class1 = double.class, bool2 = false)
65+
void returnsNullForBoxedPrimitive(Class<?> type, boolean isNull) {
66+
assertThat(Primitive.defaultValue(type) == null).isEqualTo(isNull);
67+
}
68+
}

0 commit comments

Comments
 (0)