Skip to content

Commit 233c669

Browse files
committed
feat: add support for chained field customization
1 parent 9b13f4f commit 233c669

File tree

10 files changed

+187
-18
lines changed

10 files changed

+187
-18
lines changed

src/main/java/com/github/nylle/javafixture/CustomizationContext.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
import java.util.HashMap;
55
import java.util.List;
66
import java.util.Map;
7+
import java.util.stream.Collectors;
78

89
public class CustomizationContext {
910
private final List<String> ignoredFields;
1011
private final Map<String, Object> customFields;
1112
private final boolean useRandomConstructor;
1213

13-
1414
private CustomizationContext() {
1515
ignoredFields = List.of();
1616
customFields = new HashMap<>();
@@ -38,4 +38,17 @@ public Map<String, Object> getCustomFields() {
3838
public boolean useRandomConstructor() {
3939
return useRandomConstructor;
4040
}
41+
42+
public CustomizationContext newForField(String name) {
43+
return new CustomizationContext(
44+
ignoredFields.stream()
45+
.filter(x -> x.startsWith(name + "."))
46+
.map(x -> x.substring(name.length() + 1))
47+
.collect(Collectors.toList()),
48+
customFields.entrySet().stream()
49+
.filter(x -> x.getKey().startsWith(name + "."))
50+
.map(x -> Map.entry(x.getKey().substring(name.length() + 1), x.getValue()))
51+
.collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue())),
52+
false);
53+
}
4154
}

src/main/java/com/github/nylle/javafixture/Reflector.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public Reflector<T> validateCustomization(CustomizationContext customizationCont
3333
var declaredFields = getDeclaredFields(clazz).map(field -> field.getName()).collect(toList());
3434

3535
var missingDeclaredField = Stream.concat(customizationContext.getCustomFields().keySet().stream(), customizationContext.getIgnoredFields().stream())
36+
.map(entry -> entry.replaceAll("\\..+", ""))
3637
.filter(entry -> !declaredFields.contains(entry))
3738
.findFirst();
3839

@@ -45,7 +46,9 @@ public Reflector<T> validateCustomization(CustomizationContext customizationCont
4546
.entrySet()
4647
.stream()
4748
.filter(x -> x.getValue().size() > 1)
48-
.filter(x -> Stream.concat(customizationContext.getCustomFields().keySet().stream(), customizationContext.getIgnoredFields().stream()).anyMatch(y -> y.equals(x.getKey())))
49+
.filter(x -> Stream.concat(customizationContext.getCustomFields().keySet().stream(), customizationContext.getIgnoredFields().stream())
50+
.map(entry -> entry.replaceAll("\\..+", ""))
51+
.anyMatch(y -> y.equals(x.getKey())))
4952
.findFirst();
5053

5154
if (duplicateField.isPresent()) {

src/main/java/com/github/nylle/javafixture/specimen/AbstractSpecimen.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
import com.github.nylle.javafixture.SpecimenType;
1111

1212
import java.lang.annotation.Annotation;
13-
import java.util.List;
14-
import java.util.Map;
1513

1614
public class AbstractSpecimen<T> implements ISpecimen<T> {
1715

@@ -60,9 +58,9 @@ public T create(final CustomizationContext customizationContext, Annotation[] an
6058
field.getName(),
6159
specimenFactory
6260
.build(SpecimenType.fromClass(field.getGenericType()))
63-
.create(new CustomizationContext(List.of(), Map.of(), false), field.getAnnotations()))));
61+
.create(customizationContext.newForField(field.getName()), field.getAnnotations()))));
6462
return context.remove(type);
65-
} catch(SpecimenException ex) {
63+
} catch (SpecimenException ex) {
6664
return instanceFactory.manufacture(type, customizationContext, ex);
6765
}
6866
}

src/main/java/com/github/nylle/javafixture/specimen/GenericSpecimen.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import com.github.nylle.javafixture.SpecimenType;
1111

1212
import java.lang.annotation.Annotation;
13-
import java.util.List;
1413
import java.util.Map;
1514

1615
public class GenericSpecimen<T> implements ISpecimen<T> {
@@ -82,7 +81,7 @@ private T populate(CustomizationContext customizationContext) {
8281
field.getName(),
8382
specimens.getOrDefault(
8483
field.getGenericType().getTypeName(),
85-
specimenFactory.build(SpecimenType.fromClass(field.getType()))).create(new CustomizationContext(List.of(), Map.of(), false), new Annotation[0]))));
84+
specimenFactory.build(SpecimenType.fromClass(field.getType()))).create(customizationContext.newForField(field.getName()), new Annotation[0]))));
8685
} catch (SpecimenException ex) {
8786
context.remove(type);
8887
return instanceFactory.construct(type, customizationContext);

src/main/java/com/github/nylle/javafixture/specimen/ObjectSpecimen.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
import com.github.nylle.javafixture.SpecimenType;
1111

1212
import java.lang.annotation.Annotation;
13-
import java.util.List;
14-
import java.util.Map;
1513

1614
public class ObjectSpecimen<T> implements ISpecimen<T> {
1715

@@ -68,7 +66,7 @@ private T populate(CustomizationContext customizationContext) {
6866
field.getName(),
6967
specimenFactory
7068
.build(SpecimenType.fromClass(field.getGenericType()))
71-
.create(new CustomizationContext(List.of(), Map.of(), false), reflector.getFieldAnnotations(field)))));
69+
.create(customizationContext.newForField(field.getName()), reflector.getFieldAnnotations(field)))));
7270
} catch (SpecimenException ex) {
7371
return context.overwrite(type, instanceFactory.construct(type, customizationContext));
7472
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.github.nylle.javafixture;
2+
3+
import com.github.nylle.javafixture.annotations.fixture.TestWithFixture;
4+
import com.github.nylle.javafixture.annotations.testcases.TestCase;
5+
import com.github.nylle.javafixture.annotations.testcases.TestWithCases;
6+
import org.junit.jupiter.api.Nested;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
14+
class CustomizationContextTest {
15+
16+
@Nested
17+
class NewForField {
18+
19+
@Test
20+
void returnsContextWithIgnoredNestedField() {
21+
var sut = new CustomizationContext(
22+
List.of("field", "fieldWith.nestedField", "fieldWith.nestedField2"),
23+
Map.of(),
24+
false);
25+
26+
var actual = sut.newForField("fieldWith");
27+
28+
assertThat(actual.getIgnoredFields()).containsExactly("nestedField", "nestedField2");
29+
}
30+
31+
@Test
32+
void returnsContextWithEmptyIgnoredNestedFieldWhenFieldHasNoNestedFields() {
33+
var sut = new CustomizationContext(List.of("field"), Map.of(), false);
34+
35+
var actual = sut.newForField("field");
36+
37+
assertThat(actual.getIgnoredFields()).isEmpty();
38+
}
39+
40+
@TestWithFixture
41+
void returnsContextWithCustomizedNestedField(Object expected) {
42+
var sut = new CustomizationContext(
43+
List.of(),
44+
Map.of(
45+
"field", new Object(),
46+
"fieldWith.nestedField", expected,
47+
"fieldWith.nestedField2", expected),
48+
false);
49+
50+
var actual = sut.newForField("fieldWith");
51+
52+
assertThat(actual.getCustomFields()).containsExactlyInAnyOrderEntriesOf(Map.of("nestedField", expected, "nestedField2", expected));
53+
}
54+
55+
@TestWithFixture
56+
void returnsContextWithEmptyCustomizedNestedFieldWhenFieldHasNoNestedFields(Object expected) {
57+
var sut = new CustomizationContext(
58+
List.of(),
59+
Map.of("field", new Object()),
60+
false);
61+
62+
var actual = sut.newForField("field");
63+
64+
assertThat(actual.getCustomFields()).isEmpty();
65+
}
66+
67+
@TestWithCases
68+
@TestCase(bool1 = true)
69+
@TestCase(bool1 = false)
70+
void useRandomConstructorIsAlwaysFalse(boolean useRandomConstructor, @com.github.nylle.javafixture.annotations.testcases.Fixture String fieldName) {
71+
var sut = new CustomizationContext(List.of(), Map.of(), useRandomConstructor);
72+
73+
var actual = sut.newForField(fieldName);
74+
75+
assertThat(actual.useRandomConstructor()).isFalse();
76+
}
77+
}
78+
}

src/test/java/com/github/nylle/javafixture/ReflectorTest.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void validCustomization() {
3838

3939
var sut = new Reflector<>(new GenericChild<String>());
4040

41-
var validCustomisation = new CustomizationContext(List.of(), Map.of("baseField", "foo"), false);
41+
var validCustomisation = new CustomizationContext(List.of(), Map.of("baseField.subField", "foo"), false);
4242

4343
assertThatCode(() -> sut.validateCustomization(validCustomisation, new SpecimenType<>() {}))
4444
.doesNotThrowAnyException();
@@ -50,7 +50,7 @@ void invalidCustomization() {
5050

5151
var sut = new Reflector<>(new GenericChild<String>());
5252

53-
var invalidCustomisation = new CustomizationContext(List.of(), Map.of("nonExistingField", "foo"), false);
53+
var invalidCustomisation = new CustomizationContext(List.of(), Map.of("nonExistingField.subField", "foo"), false);
5454

5555
assertThatExceptionOfType(SpecimenException.class)
5656
.isThrownBy(() -> sut.validateCustomization(invalidCustomisation, new SpecimenType<>() {}))
@@ -64,7 +64,7 @@ void customizingDuplicateFields() {
6464

6565
var sut = new Reflector<>(new GenericChild<String>());
6666

67-
Map<String, Object> customization = Map.of("fieldIn2Classes", 100.0);
67+
Map<String, Object> customization = Map.of("fieldIn2Classes.subField", 100.0);
6868

6969
var invalidCustomisation = new CustomizationContext(List.of(), customization, false);
7070

@@ -82,7 +82,7 @@ void omittingDuplicateFields() {
8282

8383
var sut = new Reflector<>(new GenericChild<String>());
8484

85-
var omitting = List.of("fieldIn2Classes");
85+
var omitting = List.of("fieldIn2Classes.subField");
8686

8787
var invalidCustomisation = new CustomizationContext(omitting, Map.of(), false);
8888

@@ -98,7 +98,8 @@ void omittingDuplicateFields() {
9898
@Nested
9999
@DisplayName("when setting a field via reflection")
100100
class SetField {
101-
@DisplayName("an IllegalAccessException is turned to a SpecimenException")
101+
102+
@DisplayName("an IllegalAccessException is turned into a SpecimenException")
102103
@Test
103104
void catchIllegalAccessException() throws Exception {
104105
var mockedField = Mockito.mock(Field.class);
@@ -109,7 +110,7 @@ void catchIllegalAccessException() throws Exception {
109110
.isThrownBy(() -> sut.setField(mockedField, ""));
110111
}
111112

112-
@DisplayName("an IllegalAccessException is turned to a SpecimenException")
113+
@DisplayName("a SecurityException is turned into a SpecimenException")
113114
@Test
114115
void catchSecurityException() {
115116
var mockedField = Mockito.mock(Field.class);
@@ -119,7 +120,7 @@ void catchSecurityException() {
119120
.isThrownBy(() -> sut.setField(mockedField, ""));
120121
}
121122

122-
@DisplayName("an InaccessibleObjectException is turned to a SpecimenException")
123+
@DisplayName("an InaccessibleObjectException is turned into a SpecimenException")
123124
@Test
124125
void catchInaccessibleObjectException() {
125126
var mockedField = Mockito.mock(Field.class);

src/test/java/com/github/nylle/javafixture/specimen/AbstractSpecimenTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
import com.github.nylle.javafixture.Configuration;
44
import com.github.nylle.javafixture.Context;
5+
import com.github.nylle.javafixture.CustomizationContext;
56
import com.github.nylle.javafixture.SpecimenException;
67
import com.github.nylle.javafixture.SpecimenFactory;
78
import com.github.nylle.javafixture.SpecimenType;
9+
import com.github.nylle.javafixture.annotations.fixture.TestWithFixture;
810
import com.github.nylle.javafixture.testobjects.TestAbstractClass;
11+
import com.github.nylle.javafixture.testobjects.TestObject;
912
import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithConcreteMethod;
1013
import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithConstructorException;
1114
import com.github.nylle.javafixture.testobjects.interfaces.InterfaceWithoutImplementation;
@@ -156,5 +159,56 @@ void resultIsNotCached() {
156159
assertThat(original.toString()).isNotEqualTo(second.toString());
157160
assertThat(original.getString()).isNotEqualTo(second.getString());
158161
}
162+
163+
@TestWithFixture
164+
void canCustomizeFieldInNestedObject(String expected) {
165+
var sut = new AbstractSpecimen<WithTestAbstractClass>(SpecimenType.fromClass(WithTestAbstractClass.class), context, specimenFactory);
166+
var customizationContext = new CustomizationContext(
167+
List.of(),
168+
Map.of("testObject.value", expected, "testAbstractClass.string", expected),
169+
false);
170+
171+
var actual = sut.create(customizationContext, new Annotation[0]);
172+
173+
assertThat(actual.getTestObject()).isNotNull();
174+
assertThat(actual.getTestObject().getValue()).isEqualTo(expected);
175+
assertThat(actual.getTestObject().getIntegers()).isNotNull();
176+
assertThat(actual.getTestObject().getStrings()).isNotEmpty();
177+
178+
assertThat(actual.getTestAbstractClass()).isNotNull();
179+
assertThat(actual.getTestAbstractClass().getString()).isEqualTo(expected);
180+
}
181+
182+
@Test
183+
void canOmitFieldInNestedObject() {
184+
var sut = new AbstractSpecimen<WithTestAbstractClass>(SpecimenType.fromClass(WithTestAbstractClass.class), context, specimenFactory);
185+
var customizationContext = new CustomizationContext(
186+
List.of("testObject.value", "testAbstractClass.string"),
187+
Map.of(),
188+
false);
189+
190+
var actual = sut.create(customizationContext, new Annotation[0]);
191+
192+
assertThat(actual.getTestObject()).isNotNull();
193+
assertThat(actual.getTestObject().getValue()).isNull();
194+
assertThat(actual.getTestObject().getIntegers()).isNotNull();
195+
assertThat(actual.getTestObject().getStrings()).isNotEmpty();
196+
197+
assertThat(actual.getTestAbstractClass()).isNotNull();
198+
assertThat(actual.getTestAbstractClass().getString()).isNull();
199+
}
200+
201+
public abstract static class WithTestAbstractClass {
202+
private TestAbstractClass testAbstractClass;
203+
private TestObject testObject;
204+
205+
public TestAbstractClass getTestAbstractClass() {
206+
return testAbstractClass;
207+
}
208+
209+
public TestObject getTestObject() {
210+
return testObject;
211+
}
212+
}
159213
}
160214

src/test/java/com/github/nylle/javafixture/specimen/GenericSpecimenTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,20 @@ void customFieldIsOnlyUsedInTopLevelObject() {
163163
assertThat(actual.getTestObject().getIntegers()).isNotEmpty();
164164
}
165165

166+
@Test
167+
void canCustomizeFieldInNestedObject() {
168+
var sut = new GenericSpecimen<>(new SpecimenType<WithTestObject<Integer>>() {}, context, specimenFactory);
169+
170+
var customizationContext = new CustomizationContext(List.of("testObject.integers"), Map.of("testObject.value", "42"), false);
171+
172+
var actual = sut.create(customizationContext, new Annotation[0]);
173+
174+
assertThat(actual.getTestObject()).isNotNull();
175+
assertThat(actual.getTestObject().getValue()).isEqualTo("42");
176+
assertThat(actual.getTestObject().getIntegers()).isNull();
177+
assertThat(actual.getTestObject().getStrings()).isNotEmpty();
178+
}
179+
166180
@Test
167181
void createdObjectsAreNotCached() {
168182
var sut = new GenericSpecimen<>(new SpecimenType<WithTestObject<Integer>>() {}, context, specimenFactory);

src/test/java/com/github/nylle/javafixture/specimen/ObjectSpecimenTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,17 @@ void customFieldIsOnlyUsedInTopLevelObject() {
162162
assertThat(actual.getTestObject().getIntegers()).isNotEmpty();
163163
}
164164

165+
@Test
166+
void canCustomizeFieldInNestedObject() {
167+
var sut = new ObjectSpecimen<WithTestObject>(SpecimenType.fromClass(WithTestObject.class), context, specimenFactory);
168+
var customizationContext = new CustomizationContext(List.of("testObject.integers"), Map.of("testObject.value", "42"), false);
169+
var actual = sut.create(customizationContext, new Annotation[0]);
170+
assertThat(actual.getTestObject()).isNotNull();
171+
assertThat(actual.getTestObject().getValue()).isEqualTo("42");
172+
assertThat(actual.getTestObject().getIntegers()).isNull();
173+
assertThat(actual.getTestObject().getStrings()).isNotEmpty();
174+
}
175+
165176
@Nested
166177
@DisplayName("when specimen has superclass")
167178
class WhenInheritance {

0 commit comments

Comments
 (0)