Skip to content

Commit 5d71f3a

Browse files
authored
Merge pull request #53 from Nylle/JAVAFIXTURE-52_set-fields-by-type
JAVAFIXTURE-52: Set fields by type
2 parents 0acb2e3 + ee4190d commit 5d71f3a

13 files changed

Lines changed: 227 additions & 32 deletions

File tree

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
public class Context {
77
private final Configuration configuration;
8-
private final Map<Integer, Object> cache = new ConcurrentHashMap<>();
8+
private final Map<Integer, Object> cache;
99

1010
public Context(Configuration configuration) {
1111

@@ -14,6 +14,17 @@ public Context(Configuration configuration) {
1414
}
1515

1616
this.configuration = configuration;
17+
this.cache = new ConcurrentHashMap<>();
18+
}
19+
20+
public Context(Configuration configuration, Map<Integer, Object> predefinedInstances) {
21+
22+
if (configuration == null) {
23+
throw new IllegalArgumentException("configuration: null");
24+
}
25+
26+
this.configuration = configuration;
27+
this.cache = new ConcurrentHashMap<>(predefinedInstances);
1728
}
1829

1930
public Configuration getConfiguration() {
@@ -32,5 +43,10 @@ public <T> T cached(SpecimenType type, T instance) {
3243
public <T> T cached(SpecimenType type) {
3344
return (T) cache.get(type.hashCode());
3445
}
46+
47+
public <T> T preDefined(SpecimenType type, T instance) {
48+
return cache.containsKey(type.hashCode()) ? (T) cache.get(type.hashCode()) : instance;
49+
}
50+
3551
}
3652

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ public interface ISpecimenBuilder<T> {
1414

1515
ISpecimenBuilder<T> with(String fieldName, Object value);
1616

17+
<U> ISpecimenBuilder<T> with(Class<U> type, U value);
18+
19+
<U> ISpecimenBuilder<T> with(SpecimenType<U> type, U value);
20+
1721
ISpecimenBuilder<T> without(String fieldName);
1822
}
1923

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.LinkedList;
55
import java.util.List;
66
import java.util.Map;
7+
import java.util.concurrent.ConcurrentHashMap;
78
import java.util.function.Consumer;
89
import java.util.stream.IntStream;
910
import java.util.stream.Stream;
@@ -12,6 +13,7 @@ public class SpecimenBuilder<T> implements ISpecimenBuilder<T> {
1213
private final List<Consumer<T>> functions = new LinkedList<>();
1314
private final List<String> ignoredFields = new LinkedList<>();
1415
private final Map<String, Object> customFields = new HashMap<>();
16+
private final Map<Integer, Object> predefinedInstances = new ConcurrentHashMap<>();
1517

1618
private final SpecimenType<T> type;
1719
private final Configuration configuration;
@@ -26,7 +28,7 @@ public SpecimenBuilder(final SpecimenType<T> type, final Configuration configura
2628
*/
2729
@Override
2830
public T create() {
29-
return customize(new SpecimenFactory(new Context(configuration)).build(type).create(new CustomizationContext(ignoredFields, customFields)));
31+
return customize(new SpecimenFactory(new Context(configuration, predefinedInstances)).build(type).create(new CustomizationContext(ignoredFields, customFields)));
3032
}
3133

3234
/**
@@ -73,6 +75,34 @@ public ISpecimenBuilder<T> with(final String fieldName, Object value) {
7375
return this;
7476
}
7577

78+
/**
79+
* Sets all fields with the specified type to the specified value during object creation.
80+
*
81+
* @param type the type of the fields to be set
82+
* @param value the value to be set to the fields
83+
* @param <U> the type of the value
84+
* @return this builder for further customisation
85+
*/
86+
@Override
87+
public <U> ISpecimenBuilder<T> with(final Class<U> type, final U value) {
88+
predefinedInstances.putIfAbsent(SpecimenType.fromClass(type).hashCode(), value);
89+
return this;
90+
}
91+
92+
/**
93+
* Sets all fields with the specified type to the specified value during object creation.
94+
*
95+
* @param type the type of the fields to be set
96+
* @param value the value to be set to the fields
97+
* @param <U> the type of the value
98+
* @return this builder for further customisation
99+
*/
100+
@Override
101+
public <U> ISpecimenBuilder<T> with(final SpecimenType<U> type, final U value) {
102+
predefinedInstances.putIfAbsent(type.hashCode(), value);
103+
return this;
104+
}
105+
76106
/**
77107
* Omits the field with the specified name during object creation
78108
* Primitives will receive their respective default-value, objects will be null

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public <T> ISpecimen<T> build(final SpecimenType<T> type) {
2626
}
2727

2828
if (type.isEnum()) {
29-
return new EnumSpecimen<>(type);
29+
return new EnumSpecimen<>(type, context);
3030
}
3131

3232
if (type.isCollection()) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public T create(final CustomizationContext customizationContext) {
7575
}
7676

7777
if (type.asClass().equals(EnumSet.class)) {
78-
return createEnumSet();
78+
return context.cached(type, createEnumSet());
7979
}
8080

8181
Collection<G> collection = context.cached(type, type.isInterface() ? createFromInterfaceType(type.asClass()) : createFromConcreteType(type));

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.nylle.javafixture.specimen;
22

3+
import com.github.nylle.javafixture.Context;
34
import com.github.nylle.javafixture.CustomizationContext;
45
import com.github.nylle.javafixture.ISpecimen;
56
import com.github.nylle.javafixture.SpecimenType;
@@ -12,8 +13,9 @@ public class EnumSpecimen<T> implements ISpecimen<T> {
1213

1314
private final SpecimenType<T> type;
1415
private final Random random;
16+
private final Context context;
1517

16-
public EnumSpecimen(final SpecimenType<T> type) {
18+
public EnumSpecimen(final SpecimenType<T> type, final Context context) {
1719

1820
if (type == null) {
1921
throw new IllegalArgumentException("type: null");
@@ -23,8 +25,13 @@ public EnumSpecimen(final SpecimenType<T> type) {
2325
throw new IllegalArgumentException("type: " + type.getName());
2426
}
2527

28+
if (context == null) {
29+
throw new IllegalArgumentException("context: null");
30+
}
31+
2632
this.type = type;
2733
this.random = new Random();
34+
this.context = context;
2835
}
2936

3037
@Override
@@ -34,6 +41,6 @@ public T create() {
3441

3542
@Override
3643
public T create(CustomizationContext customizationContext) {
37-
return type.getEnumConstants()[random.nextInt(type.getEnumConstants().length)];
44+
return context.preDefined(type, type.getEnumConstants()[random.nextInt(type.getEnumConstants().length)]);
3845
}
3946
}

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

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class PrimitiveSpecimen<T> implements ISpecimen<T> {
1515
private final SpecimenType<T> type;
1616
private final PseudoRandom pseudoRandom;
1717
private final Configuration configuration;
18+
private final Context context;
1819

1920
public PrimitiveSpecimen(final SpecimenType<T> type, final Context context) {
2021

@@ -26,9 +27,14 @@ public PrimitiveSpecimen(final SpecimenType<T> type, final Context context) {
2627
throw new IllegalArgumentException("type: " + type.getName());
2728
}
2829

30+
if (context == null) {
31+
throw new IllegalArgumentException("context: null");
32+
}
33+
2934
this.type = type;
3035
this.pseudoRandom = new PseudoRandom();
3136
this.configuration = context.getConfiguration();
37+
this.context = context;
3238
}
3339

3440
@Override
@@ -39,39 +45,39 @@ public T create() {
3945
@Override
4046
public T create(final CustomizationContext customizationContext) {
4147
if (type.asClass().equals(String.class)) {
42-
return (T) pseudoRandom.nextString();
48+
return (T) context.preDefined(type, pseudoRandom.nextString());
4349
}
4450

4551
if (type.asClass().equals(Boolean.class) || type.asClass().equals(boolean.class)) {
46-
return (T) pseudoRandom.nextBool();
52+
return (T) context.preDefined(type, pseudoRandom.nextBool());
4753
}
4854

4955
if (type.asClass().equals(Character.class) || type.asClass().equals(char.class)) {
50-
return (T) pseudoRandom.nextChar();
56+
return (T) context.preDefined(type, pseudoRandom.nextChar());
5157
}
5258

5359
if (type.asClass().equals(Byte.class) || type.asClass().equals(byte.class)) {
54-
return (T) pseudoRandom.nextByte();
60+
return (T) context.preDefined(type, pseudoRandom.nextByte());
5561
}
5662

5763
if (type.asClass().equals(Short.class) || type.asClass().equals(short.class)) {
58-
return (T) pseudoRandom.nextShort(configuration.usePositiveNumbersOnly());
64+
return (T) context.preDefined(type, pseudoRandom.nextShort(configuration.usePositiveNumbersOnly()));
5965
}
6066

6167
if (type.asClass().equals(Integer.class) || type.asClass().equals(int.class)) {
62-
return (T) pseudoRandom.nextInt(configuration.usePositiveNumbersOnly());
68+
return (T) context.preDefined(type, pseudoRandom.nextInt(configuration.usePositiveNumbersOnly()));
6369
}
6470

6571
if (type.asClass().equals(Long.class) || type.asClass().equals(long.class)) {
66-
return (T) pseudoRandom.nextLong(configuration.usePositiveNumbersOnly());
72+
return (T) context.preDefined(type, pseudoRandom.nextLong(configuration.usePositiveNumbersOnly()));
6773
}
6874

6975
if (type.asClass().equals(Float.class) || type.asClass().equals(float.class)) {
70-
return (T) pseudoRandom.nextFloat(configuration.usePositiveNumbersOnly());
76+
return (T) context.preDefined(type, pseudoRandom.nextFloat(configuration.usePositiveNumbersOnly()));
7177
}
7278

7379
if (type.asClass().equals(Double.class) || type.asClass().equals(double.class)) {
74-
return (T) pseudoRandom.nextDouble(configuration.usePositiveNumbersOnly());
80+
return (T) context.preDefined(type, pseudoRandom.nextDouble(configuration.usePositiveNumbersOnly()));
7581
}
7682

7783
throw new SpecimenException("Unsupported type: " + type);

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

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ public class TimeSpecimen<T> implements ISpecimen<T> {
2727
private final Random random;
2828
private final Context context;
2929

30-
public TimeSpecimen(final SpecimenType<T> type, Context context) {
31-
this.context = context;
30+
public TimeSpecimen(final SpecimenType<T> type, final Context context) {
3231
if (type == null) {
3332
throw new IllegalArgumentException("type: null");
3433
}
@@ -43,6 +42,7 @@ public TimeSpecimen(final SpecimenType<T> type, Context context) {
4342

4443
this.type = type;
4544
this.random = new Random();
45+
this.context = context;
4646
}
4747

4848
@Override
@@ -55,42 +55,41 @@ public T create(final CustomizationContext customizationContext) {
5555
if (Temporal.class.isAssignableFrom(type.asClass())) {
5656
try {
5757
Method now = type.asClass().getMethod("now", Clock.class);
58-
return (T) now.invoke(null, context.getConfiguration().getClock());
58+
return (T) context.preDefined(type, now.invoke(null, context.getConfiguration().getClock()));
5959
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
6060
throw new SpecimenException("Unsupported type: " + type.asClass());
6161
}
6262
}
6363

6464
if (type.asClass().equals(java.util.Date.class)) {
65-
return (T) java.sql.Timestamp.valueOf(LocalDateTime.now(context.getConfiguration().getClock()));
65+
return (T) context.preDefined(type, java.sql.Timestamp.valueOf(LocalDateTime.now(context.getConfiguration().getClock())));
6666
}
6767

68-
6968
if (type.asClass().equals(java.sql.Date.class)) {
70-
return (T) java.sql.Date.valueOf(LocalDateTime.now(context.getConfiguration().getClock()).toLocalDate());
69+
return (T) context.preDefined(type, java.sql.Date.valueOf(LocalDateTime.now(context.getConfiguration().getClock()).toLocalDate()));
7170
}
7271

7372
if (type.asClass().equals(MonthDay.class)) {
74-
return (T) MonthDay.now(context.getConfiguration().getClock());
73+
return (T) context.preDefined(type, MonthDay.now(context.getConfiguration().getClock()));
7574
}
7675

7776
if (type.asClass().equals(JapaneseEra.class)) {
78-
return (T) JapaneseEra.values()[random.nextInt(JapaneseEra.values().length)];
77+
return (T) context.preDefined(type, JapaneseEra.values()[random.nextInt(JapaneseEra.values().length)]);
7978
}
8079

8180
if (type.asClass().equals(ZoneOffset.class)) {
82-
return (T) ZoneOffset.ofHours(new Random().nextInt(19));
81+
return (T) context.preDefined(type, ZoneOffset.ofHours(new Random().nextInt(19)));
8382
}
8483
if (type.asClass().equals(Duration.class)) {
85-
return (T) Duration.ofDays(random.nextInt());
84+
return (T) context.preDefined(type, Duration.ofDays(random.nextInt()));
8685
}
8786

8887
if (type.asClass().equals(Period.class)) {
89-
return (T) Period.ofDays(random.nextInt());
88+
return (T) context.preDefined(type, Period.ofDays(random.nextInt()));
9089
}
9190

9291
if (type.asClass().equals(ZoneId.class)) {
93-
return (T) ZoneId.of(ZoneId.getAvailableZoneIds().iterator().next());
92+
return (T) context.preDefined(type, ZoneId.of(ZoneId.getAvailableZoneIds().iterator().next()));
9493
}
9594

9695
throw new SpecimenException("Unsupported type: " + type.asClass());

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.github.nylle.javafixture.testobjects.TestClassWithNestedClasses;
66
import com.github.nylle.javafixture.testobjects.TestEnum;
77
import com.github.nylle.javafixture.testobjects.TestObjectGeneric;
8+
import com.github.nylle.javafixture.testobjects.TestObjectWithDeepNesting;
89
import com.github.nylle.javafixture.testobjects.TestObjectWithEnumMap;
910
import com.github.nylle.javafixture.testobjects.TestObjectWithEnumSet;
1011
import com.github.nylle.javafixture.testobjects.TestObjectWithGenericConstructor;
@@ -207,6 +208,18 @@ void canOmitPrivatePrimitiveFieldAndInitializesDefaultValue() {
207208
assertThat(result.getPrimitive()).isEqualTo(0);
208209
}
209210

211+
@Test
212+
void canBeCustomizedWithType() {
213+
var expected = Period.ofDays(42);
214+
var result = fixture().build(Contract.class)
215+
.with(Period.class, expected)
216+
.create();
217+
218+
var actual = result.getBaseContractPosition().getRemainingPeriod();
219+
220+
assertThat(actual).isSameAs(expected);
221+
}
222+
210223
@Test
211224
void canPerformAction() {
212225
Fixture fixture = new Fixture(configuration);
@@ -506,6 +519,21 @@ void canBeCustomized() {
506519
assertThat(result.getPrimitiveInt()).isEqualTo(0);
507520
}
508521

522+
@Test
523+
void canBeCustomizedWithType() {
524+
Fixture sut = new Fixture(configuration);
525+
526+
var expected = new TestObjectGeneric<String, Integer>();
527+
528+
var result = sut.build(TestObjectWithDeepNesting.class)
529+
.with(new SpecimenType<>() {}, expected)
530+
.create();
531+
532+
var actual = result.getNested().getGeneric();
533+
534+
assertThat(actual).isSameAs(expected);
535+
}
536+
509537
@Test
510538
void createThroughRandomConstructor() {
511539

0 commit comments

Comments
 (0)