Skip to content

Commit

Permalink
fix: create collections with unique elements #102 (WIP)
Browse files Browse the repository at this point in the history
This is a dirty hack, just do show that it is possible.
This still needs work.
  • Loading branch information
akutschera committed Aug 11, 2024
1 parent 418d01c commit c482aeb
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 28 deletions.
67 changes: 39 additions & 28 deletions src/main/java/com/github/nylle/javafixture/Fixture.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public Fixture() {
*
* @param configuration the configuration
*/
public Fixture(Configuration configuration) {
public Fixture( Configuration configuration ) {
this.configuration = configuration;
}

Expand Down Expand Up @@ -67,101 +67,112 @@ public static Configuration configuration() {
* Creates a new object of the specified type, recursively populated with random values
*
* @param type the {@code Class<T>} based on which the object is created
* @param <T> the type of the object to be created
* @param <T> the type of the object to be created
* @return a new object of the specified {@code Class<T>}
*/
public <T> T create(final Class<T> type) {
return new SpecimenBuilder<T>(SpecimenType.fromClass(type), configuration).create();
public <T> T create( final Class<T> type ) {
return create(SpecimenType.fromClass(type));
}

/**
* Creates a new optional of the specified type, recursively populated with random values
*
* @param type the {@code Class<T>} based on which the object is created
* @param <T> the type of the object to be created
* @param <T> the type of the object to be created
* @return a new {@code Optional} of the specified {@code Class<T>}
*
* <p>
* This feature is deprecated without replacement.
*/
@Deprecated(forRemoval = true)
public <T> Optional<T> createOptional(final Class<T> type) {
public <T> Optional<T> createOptional( final Class<T> type ) {
return new SpecimenBuilder<T>(SpecimenType.fromClass(type), configuration).createOptional();
}

/**
* Creates a new object of the specified type, recursively populated with random values
*
* @param type the {@code SpecimenType<T>} based on which the object is created
* @param <T> the type of the object to be created
* @param <T> the type of the object to be created
* @return a new object of the specified type {@code T}
*/
public <T> T create(final SpecimenType<T> type) {
return new SpecimenBuilder<>(type, configuration).create();
public <T> T create( final SpecimenType<T> type ) {
var res = new SpecimenBuilder<>(type, configuration).create();
if ( type.isCollection() ) {
var o = ( (Collection<?>) res ).toArray()[0];
var elems = new SpecimenBuilder<>(
SpecimenType.fromClass(
o.getClass()),
configuration).createMany(configuration.getRandomCollectionSize())
.collect(Collectors.toList());
( (Collection) res ).clear();
( (Collection) res ).addAll(elems);
}
return res;
}

/**
* Creates a new object of the specified type, using a random constructor if available
*
* @param type the {@code Class<T>} based on which the object is created
* @param <T> the type of the object to be created
* @param <T> the type of the object to be created
* @return a new object of the specified type {@code T}
*/
public <T> T construct(final Class<T> type) {
public <T> T construct( final Class<T> type ) {
return new SpecimenBuilder<T>(SpecimenType.fromClass(type), configuration).construct();
}

/**
* Creates a new object of the specified type, using a random constructor if available
*
* @param type the {@code SpecimenType<T>} based on which the object is created
* @param <T> the type of the object to be created
* @param <T> the type of the object to be created
* @return a new object of the specified type {@code T}
*/
public <T> T construct(final SpecimenType<T> type) {
public <T> T construct( final SpecimenType<T> type ) {
return new SpecimenBuilder<>(type, configuration).construct();
}

/**
* Creates a {@code Stream} of objects of the specified type, recursively populated with random values
*
* @param type the {@code Class<T>} based on which the objects are created
* @param <T> the type of the objects to be created
* @param <T> the type of the objects to be created
* @return a {@code Stream} of objects of the specified type {@code T}
*/
public <T> Stream<T> createMany(final Class<T> type) {
public <T> Stream<T> createMany( final Class<T> type ) {
return new SpecimenBuilder<T>(SpecimenType.fromClass(type), configuration).createMany();
}

/**
* Creates a {@code Stream} of objects of the specified type, recursively populated with random values
*
* @param type the {@code SpecimenType<T>} based on which the objects are created
* @param <T> the type of the objects to be created
* @param <T> the type of the objects to be created
* @return a {@code Stream} of objects of the specified type {@code T}
*/
public <T> Stream<T> createMany(final SpecimenType<T> type) {
public <T> Stream<T> createMany( final SpecimenType<T> type ) {
return new SpecimenBuilder<>(type, configuration).createMany();
}

/**
* Creates a {@code ISpecimenBuilder<T>} to customise the object of type {@code T} to be created
*
* @param type the {@code Class<T>} based on which the object is created
* @param <T> the type of the object to be created
* @param <T> the type of the object to be created
* @return a builder for further customisation
*/
public <T> ISpecimenBuilder<T> build(final Class<T> type) {
public <T> ISpecimenBuilder<T> build( final Class<T> type ) {
return new SpecimenBuilder<>(SpecimenType.fromClass(type), configuration);
}

/**
* Creates a {@code ISpecimenBuilder<T>} to customise the object of type {@code T} to be created
*
* @param type the {@code SpecimenType<T>} based on which the object is created
* @param <T> the type of the object to be created
* @param <T> the type of the object to be created
* @return a builder for further customisation
*/
public <T> ISpecimenBuilder<T> build(final SpecimenType<T> type) {
public <T> ISpecimenBuilder<T> build( final SpecimenType<T> type ) {
return new SpecimenBuilder<>(type, configuration);
}

Expand All @@ -170,10 +181,10 @@ public <T> ISpecimenBuilder<T> build(final SpecimenType<T> type) {
* The number of objects created is specified in the {@code Configuration} under {@code streamSize} (default: 3)
*
* @param result the collection the created objects should be added to
* @param type the {@code Class<T>} based on which the objects are created
* @param <T> the type of the collection and the objects to be added
* @param type the {@code Class<T>} based on which the objects are created
* @param <T> the type of the collection and the objects to be added
*/
public <T> void addManyTo(Collection<T> result, final Class<T> type) {
public <T> void addManyTo( Collection<T> result, final Class<T> type ) {
result.addAll(new SpecimenBuilder<T>(SpecimenType.fromClass(type), configuration).createMany().collect(Collectors.toList()));
}

Expand All @@ -182,10 +193,10 @@ public <T> void addManyTo(Collection<T> result, final Class<T> type) {
* The number of objects created is specified in the {@code Configuration} under {@code streamSize} (default: 3)
*
* @param result the collection the created objects should be added to
* @param type the {@code SpecimenType<T>} based on which the objects are created
* @param <T> the type of the collection and the objects to be added
* @param type the {@code SpecimenType<T>} based on which the objects are created
* @param <T> the type of the collection and the objects to be added
*/
public <T> void addManyTo(Collection<T> result, final SpecimenType<T> type) {
public <T> void addManyTo( Collection<T> result, final SpecimenType<T> type ) {
result.addAll(new SpecimenBuilder<>(type, configuration).createMany().collect(Collectors.toList()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

Expand Down Expand Up @@ -39,6 +40,11 @@ void useAnnotationProperties(Long positiveValue, List<Integer> collection) {
collection.forEach( member -> assertThat(member).isPositive());
}

@FixturedTest(minCollectionSize = 2, maxCollectionSize = 2)
void setsShouldContainEnoughElements( Set<Object> sut) {
assertThat(sut).hasSize(2);
}

@FixturedTest
void withoutParametersItIsJustAnotherTest() {
assertThat(true).isTrue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

Expand Down Expand Up @@ -34,4 +35,12 @@ void injectTempDirViaJunit(Integer intValue, @TempDir Path injectedTempDir) {
assertThat(injectedTempDir).isEqualTo(tempPath);
assertThat( intValue ).isNotNull();
}

@TestWithFixture(minCollectionSize = 3, maxCollectionSize = 9)
@DisplayName("sets should have at least minCollectionSize and at most MaxCollectionSize elements")
void setsHaveEnoughElements( Set<Object> sut ) {
assertThat(sut).hasSizeGreaterThanOrEqualTo(3)
.hasSizeLessThanOrEqualTo(9);

}
}

2 comments on commit c482aeb

@jk-idealo
Copy link
Collaborator

@jk-idealo jk-idealo commented on c482aeb Aug 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, because it circumvents the cache by creating a new Context for every element. However, if you want to create a collection of collections, e.g. List<List<Object>>, the inner lists will still only ever have duplicate elements of the same instance, or Set<Set<Object>> will have wrong inner set-sizes, I expect.

I like the idea though; maybe we can leverage it by going a few steps deeper: In the CollectionSpecimen we build a new specimen for the inner type:

if (type.isParameterized()) {
    this.specimen = specimenFactory.build(type.getGenericTypeArgument(0));
}

Maybe we can create a new SpecimenFactory at this level, instead of using the existing instance. We could pass a copy of the existing Context down with the cache cleaned.

Current:

IntStream.range(0, context.getConfiguration().getRandomCollectionSize())
        .boxed()
        .filter(x -> specimen != null)
        .forEach(x -> collection.add(
            specimen.create(customizationContext, new Annotation[0])));

Idea:

IntStream.range(0, context.getConfiguration().getRandomCollectionSize())
        .boxed()
        .filter(x -> specimen != null)
        .forEach(x -> collection.add(
            new SpecimenFactory(new Context(context.getConfiguration())).build(type.getGenericTypeArgument(0)).create(customizationContext, new Annotation[0])));

Problem is, that we lose the "predefined" instances - those are customised, e.g. if the user wants all String instances to be "foo". Maybe we can separate the general cache from the predefined instances?

@akutschera
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will solve the problem of sets with only one member.
Unfortunately it creates the problem with the circular dependency (see

).

Please sign in to comment.