Skip to content

Latest commit

 

History

History
323 lines (282 loc) · 10.7 KB

README.md

File metadata and controls

323 lines (282 loc) · 10.7 KB

Tests Maven Central MIT license

JavaFixture

JavaFixture is the attempt to bring the incredibly easy usage of Mark Seemann's AutoFixture for .NET to the Java world.

The purpose of this project is to generate full object graphs for use in test suites.

Contents

Getting Started

<dependency>
    <groupId>com.github.nylle</groupId>
    <artifactId>javafixture</artifactId>
    <version>2.9.8</version>
    <scope>test</scope>
</dependency>

Usage

Create a Fixture

Via constructor:

var fixture = new Fixture();

...or using a static factory method for convenience:

var fixture = Fixture.fixture();

Autogenerated String

String result = fixture.create(String.class);

Sample Result

String: "c3932f6f-59ae-43df-8ee9-8788474a3f87"

Note

If a String-field is annotated with @Size or @Column, those limits will be respected:

public class TestObjectWithJavaxValidationAnnotations {
    @Size(min = 3, max = 6)
    private String withMinMaxAnnotation;

    @Column(length = 6)
    private String withColumnLengthAnnotation;
}

Autogenerated Number

int result = fixture.create(int.class);

Sample Result

int: -1612385443

Complex Type

ParentDto result = fixture.create(ParentDto.class);

Sample Result

  • ParentDto:
    • id: String: "4ed0f3c4-5ea3-4dbb-b31c-f92c036af463"
    • child: ChildDto:
      • id: String: "c3932f6f-59ae-43df-8ee9-8788474a3f87"
      • names: ArrayList:
        • String: "9452541b-c6f9-4316-b254-28d00b327d0d"
        • String: "52ac46e4-1b21-40c8-9213-31fc839fbdf7"
        • String: "333af3f6-4ed1-4580-9cae-aaee271d7ba7"

Collection of Strings

List<String> result = fixture.createMany(String.class).collect(Collectors.toList());

Sample Result

ArrayList:

  • String: "333af3f6-4ed1-4580-9cae-aaee271d7ba7"
  • String: "9452541b-c6f9-4316-b254-28d00b327d0d"
  • String: "4ed0f3c4-5ea3-4dbb-b31c-f92c036af463"

Collection of Strings with Desired Size

List<String> result = fixture.build(String.class).createMany(4).collect(Collectors.toList());

Sample Result

ArrayList:

  • String: "333af3f6-4ed1-4580-9cae-aaee271d7ba7"
  • String: "9452541b-c6f9-4316-b254-28d00b327d0d"
  • String: "4ed0f3c4-5ea3-4dbb-b31c-f92c036af463"
  • String: "52ac46e4-1b21-40c8-9213-31fc839fbdf7"

Add to Collection

List<String> result = new ArrayList<>();
result.add("HELLO!");
fixture.addManyTo(result, String.class);

Sample Result

ArrayList:

  • String: "HELLO!"
  • String: "333af3f6-4ed1-4580-9cae-aaee271d7ba7"
  • String: "9452541b-c6f9-4316-b254-28d00b327d0d"
  • String: "4ed0f3c4-5ea3-4dbb-b31c-f92c036af463"

Customize Property With Lambda

TestDto result = fixture.build(TestDto.class)
                        .with(x -> x.setMyPrivateField("HELLO!"))
                        .with(x -> x.myPublicField = 123)
                        .create();

Sample Result

TestDto:

  • myPrivateField: String: "HELLO!"
  • myPublicField: int: 123

Set Private Field Using Reflection

TestDto result = fixture.build(TestDto.class)
                        .with("myPrivateField", "HELLO!")
                        .create();

Sample Result

TestDto:

  • myPrivateField: String: "HELLO!"
  • myPublicField: int: 26123854

Set all fields for type

ParentDto result = fixture.build(ParentDto.class)
                          .with(String.class, "hello")
                          .create();

Sample Result

  • ParentDto:
    • id: String: "hello"
    • child: ChildDto:
      • id: String: "hello"
      • names: ArrayList:
        • String: "hello"
        • String: "hello"
        • String: "hello"

Omit Field

TestDto result = fixture.build(TestDto.class)
                        .without("myPrivateField")
                        .without("myPublicField")
                        .create();

Sample Result

TestDto:

  • myPrivateField: String: null
  • myPublicField: int: 0

Note

Primitives will receive their default-value, classes will be null.

Generics

Due to Java's type erasure (further reading: baeldung), it is difficult to reflect generic classes on runtime and the following doesn't work:

fixture.create(Optional<String>.class); // does not compile

Using JavaFixture however it can be achieved through a little trick:

Optional<String> result = fixture.create(new SpecimenType<Optional<String>>(){});

Please note the empty curly braces ({}) after the call to the constructor of SpecimenType. These are necessary for generic reflection through an abstract superclass.

SpecimenType can also be used for non-generic classes, but will lose any parametrisation for generic classes:

Optional result = fixture.create(SpecimenType.fromClass(Optional.class));

Optional result = fixture.create(Optional.class); // convenience method for above

Constructor

There might be some cases when you want to create an object not by instantiating it and setting all fields, but by calling one of its constructors and feeding it random values.

var result = fixture.construct(new SpecimenType<MyGeneric<String>>(){});

var result = fixture.construct(String.class);

Keep in mind that only public constructors are allowed.

Configuration

The values below are the default values, used when no configuration is provided.

var config = Configuration.configure()
                          .collectionSizeRange(2, 10)
                          .streamSize(3)
                          .usePositiveNumbersOnly(false)
                          .clock(Clock.fixed(Instant.now(), ZoneOffset.UTC));

var fixture = new Fixture(config);

The fixture can also be configured fluently inline:

var configuredFixture = Fixture.configuration()
                               .collectionSizeRange(2, 10)
                               .streamSize(3)
                               .usePositiveNumbersOnly(false)
                               .clock(Clock.fixed(Instant.now(), ZoneOffset.UTC))
                               .fixture();
  • collectionSizeRange determines the range from which a random collection size will be picked when creating any collection, map or array
  • streamSize determines the number of objects to be returned when using Stream<T> Fixture.createMany(Class<T>)
  • usePositiveNumbersOnly defines whether to generate only positive numbers including 0 for short, int, long, float, and double
  • clock sets the clock to use when creating time-based values

JUnit5 Support

In order to use JUnit5 support you need to add the following dependencies if not already present.

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.5.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.5.1</version>
    <scope>test</scope>
</dependency>

Remember to also include the vintage dependency if you still have JUnit4-tests, otherwise they won't be run.

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <version>5.5.1</version>
    <scope>test</scope>
</dependency>

Inject Random Values Into Single Test

All arguments of the test-method below will be provided as a random object generated by JavaFixture.

@TestWithFixture
void injectParameterViaMethodExtension(TestDto testObject, int intValue, Optional<String> genericObject) {
    assertThat(testObject).isInstanceOf(TestDto.class);

    assertThat(intValue).isBetween(Integer.MIN_VALUE, Integer.MAX_VALUE);

    assertThat(genericObject).isInstanceOf(Optional.class);
    assertThat(genericObject).isPresent();
    assertThat(genericObject.get()).isInstanceOf(String.class);
}

Additional annotated arguments are allowed:

@TestWithFixture
@DisplayName("Annotated parameters will work when they are at the end of the list")
void injectTempDirViaJunit(Integer intValue, @TempDir Path injectedTempDir) {
    assertThat(injectedTempDir).isEqualTo(tempPath);
    assertThat(intValue).isNotNull();
}

You can also configure Fixture inline:

@TestWithFixture(minCollectionSize = 11, maxCollectionSize = 11, positiveNumbersOnly = true)
void configurableFixture(List<Integer> input) {
    assertThat(input).hasSize(11);
    assertThat(input).allMatch(x -> x > 1);
}

Parameterized Tests

For some syntactic sugar, this library comes with a wrapper for JUnit5's parameterized test feature, called @TestWithCases.

@TestWithCases
@TestCase(str1 = "", int2 = 0)
@TestCase(str1 = " ", int2 = 1)
@TestCase(str1 = "foo", int2 = 3)
@TestCase(str1 = "hello", int2 = 5)
void testStringLength(String input, int expected) {
    assertThat(input.length()).isEqualTo(expected);
}

The test will be run for every @TestCase-annotation injecting the provided values into the test's arguments.

Due to Java's limited annotation design, the following rules apply:

  • Values can only be of type String, Class or primitive like int, boolean, float, etc.
  • Annotation parameters are indexed and they must fit to the test method argument. Example: str1 = "foo" can only be applied to the first argument which must be of type String, while int2 = 3 can only be applied to the second argument which obviously must be of type int and so on.
  • The current implementation only supports up to 6 arguments per test method.

Parameterized Tests With Random Injected Values

If you are using @TestWithFixture and want to make the test parameterized, you can do so by using the annotation @Fixture inline:

@TestWithCases
@TestCase(str1 = "foo")
@TestCase(str1 = "bar")
void testStringLength(String input, @Fixture TestDto fixture) {
    assertThat(input).hasSize(3);
    assertThat(fixture).isNotNull();
}

The test will be run for every @TestCase-annotation injecting the provided and the randomly generated values into the test's arguments.

The random values will be identical for all test-cases!