- Core
<dependency>
<groupId>com.navercorp.fixturemonkey</groupId>
<artifactId>fixture-monkey-starter</artifactId>
<version>0.5.0</version>
</dependency>- Bean Validations support
<dependency>
<groupId>com.navercorp.fixturemonkey</groupId>
<artifactId>fixture-monkey-javax-validation</artifactId>
<version>0.5.0</version>
</dependency> Method 1: Through Constructor
-
Requires each object to be mocked to be either:
- A
recordclass - Any constructors with
@ConstructorProperties- If using
lombok:- Can annotate class with
@Dataor@ConstructorProperties - Can also just add
lombok.anyConstructor.addConstructorProperties=truein lombok.config
- Can annotate class with
- If using
- A
-
Create Fixture Monkey with
objectIntrospectoroption
FixtureMonkey fixtureMonkey=FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.build();Method 2: Through Factory
- Requires each object to be mocked to have a static factory method
- Create Fixture Monkey with
objectIntrospectoroption
FixtureMonkey fixtureMonkey=FixtureMonkey.builder()
.objectIntrospector(FactoryMethodArbitraryIntrospector.INSTANCE)
.build();Method 3: Through Jackson
- Requires each object to be mocked to be serializable/deserializable by Jackson
- Requires additional dependency:
com.navercorp.fixturemonkey:fixture-monkey-jackson - Create Fixture Monkey with
pluginoption
FixtureMonkey fixtureMonkey=FixtureMonkey.builder()
.plugin(new JacksonPlugin(objectMapper))
.build();Method 1: Through Constructor (Same as the immutable type above)
Method 2: Through FieldReflection
- Requires each object to be mocked to have:
- No args constructor
- Getter / Setter
- Create Fixture Monkey with
objectIntrospectoroption
FixtureMonkey fixtureMonkey=FixtureMonkey.builder()
.objectIntrospector(FieldReflectionArbitraryIntrospector.INSTANCE)
.build();Method 3: Through Java Bean (Default objectIntrospector)
- Requires each object to be mocked to have:
- No args constructor
- Setter
See Fixture Monkey Component for the full list of features
- Read more in Fixture Monkey Arbitrary Validator
- Invalid instance could not be generated
- If an object is invalid which sampled over 10k times
from
Arbitrary,TooManyFilterMissesExceptionwill be thrown - See below in "@Pattern" bug section for a sample log
- If an object is invalid which sampled over 10k times
from
- By default, there is no validator included
Bean Validation by Javax
- To enable bean validations by
Javax, addfixture-monkey-javax-validationdependency - Add
pluginoption
FixtureMonkey fixtureMonkey=FixtureMonkey.builder()
.plugin(new JavaxValidationPlugin())
.build();Bean Validation by Jakarta
- To enable bean validations by
Javax, addfixture-monkey-jakarta-validationdependency - Add
pluginoption
FixtureMonkey fixtureMonkey=FixtureMonkey.builder()
.plugin(new JakartaValidationPlugin())
.build();Arbitrary Validation
- Current version only have
DefaultArbitraryValidatorwhichjavax.validation.Validatorwould validate instance - Can create own custom
Arbitrary Validatorby implementingArbitraryValidator - Can combine
Arbitrary ValidatorswithCompositeFixtureValidator - Create
FixtureMonkeywitharbitraryValidatoroption
- Determines when a
nullinstance is created withdefaultNullInjectGeneratoroption
- This is an alternative from using
Arbitraryto create custom logic for generation of mock data - However, to date, there are no documentations on the usage of
InnerSpec
-
Arbitrary is a fixture in
jqwik- It is used in fixture monkey to generate random data for the different types of data in the properties of Java object
-
To use
Arbitraryin Fixture Monkey:- Create a Fixture Monkey generator for the object that we want to generate
FixtureMonkey fixtureMonkey = FixtureMonkey.builder().build();
- Create
Arbitraryfor whatever fields we want to have custom generation logic
- E.g.:
Arbitrary<String> genderArbitrary = Arbitraries.of(gender);
- Use
ArbitraryBuilderto set the fields usingArbitrarycreated
ArbitraryBuilder<Person> personArbitraryBuilder = fixtureMonkey.giveMeBuilder(Person.class) .set("gender", Arbitraries.of(genderArbitrary)) ...
- Create the object
Person person = personArbitraryBuilder.build().sample();
-
See Arbitrary Component for more details
- Filtering
- Include only part of the values generated by Arbitrary
Arbitrary.filter(predicate)
- Mapping
- Start with existing arbitrary and use its generated values to build other objects from them
Arbitrary.map(function)
See Jqwik Constraining Default Generation for more details
- Default parameter generation can be constrained with these additional annotations
- Allow null values with probability
@WithNull(value): Injectnullwith probability of value- Works for all generated types
- Set string length
@StringLength(value, min, max): Set fixed length / range length@NotEmpty
- Set string not blank
@NotBlank
- Characters
@LowerChars@NumericChars
- Set Collection (List, Set, Stream, Iterator, Map, Array) size
@Size(value, min, max): Set fixed length / range length
- Set uniqueness in container object (List, Set, Stream, Iterator, Array)
@UniqueElements(by): Ensure elements are unique or unique in relation to a certain feature (by)
- Jqwik can handle type variables and wildcard types as well
See Handling of parameterized Types for more details
- Can also create own annotations apart from using Jqwik built-in annotations
- See Self-Made Annotations for more details
- Create
ListArbitrary - For specific list of values, pass in through
Arbitraries.of(<list of values>)
List<String> countries=["Singapore","Malaysia",...];
ListArbitrary<String> countryArbitrary=Arbitraries.of(countries).list(); - Useful properties:
.uniqueElements(): create unique elements in list.ofMaxSize(int maxSize),.ofMinSize(int minSize).ofSize(int size): fix a size- See https://jqwik.net/docs/1.8.0/javadoc/net/jqwik/api/arbitraries/ListArbitrary.html
- Unable to simply just create superclass when its
abstracttype, i.e. cannot simply justfixtureMonkey.giveMeOne(CommsInfo.class)whereCommsInfois an abstract class - One possible approach - creating minimal
Arbitraryfor each concrete class:
Arbitrary<OnlineAccount> onlineAccountArbitrary=
fixtureMonkey.giveMeBuilder(OnlineAccount.class).build();
Arbitrary<InternetSubscriptionAccount> internetSubscriptionArbitrary=
fixtureMonkey.giveMeBuilder(InternetSubscriptionAccount.class).build();
Arbitrary<CommsInfo> commsInfoArbitrary=
Arbitraries.oneOf(onlineAccountArbitrary,internetSubscriptionArbitrary);- Cannot simply do this to have custom logic for nested fields:
fixtureMonkey.giveMeBuilder(Person.class)
.set("hobbies.hobby",hobbyArbitrary) // where hobbies is a nested field- Can use Web Module (
jqwik-web) to generate data of email format from- Create
EmailArbitraryusingWeb.emails()
- Create
Time
- Can use Time Module(
jqwik-time) to generate date/time-related java types
- Bean validations will be overridden with Arbitraries
- E.g.: Cannot use
@Emailon a field that we are going to overrride with Arbitraries (such asArbitraries.strings().ofMaxLength(20))), it will not be possible to generate mock data the fits the@Emailconstraint yet also abiding to the Arbitraries attributes
- E.g.: Cannot use
- To override / custom generator for different fields, we have to pass in the
fieldNamein string, like
ArbitraryBuilder<Person> personArbitraryBuilder=fixtureMonkey.giveMeBuilder(Person.class)
.set("gender",Arbitraries.of(genderArbitrary))
...- There are thus risks that the field name may not be valid / present in the object to be mocked
- Create Fixture Monkey with
useExpressionStrictMode = trueoption to throw exception if the property does not exist
- I believe is similar issue as Github issue on Jarkata Validation not working
@Pattern(regexp = "^[FM]?$") // javax.validation.constraints.Pattern
private String gender;- Threw exception, which indicates mocked data was not created based off the
@Patternconstraint
22:25: 48.792 [main] ERROR c.n.f.r.ArbitraryValue$MonkeyRandomGenerator - Fail to create valid arbitrary.
Fixture factory Constraint Violation messages.
- violation: must match "^[FM]?$", type: class com.lqr.javafixture.domain.Person, property: updatedBy, invalidValue: ^M$
javax.validation.ConstraintViolationException: DefaultArbitrayValidator ConstraintViolations. type: class com.lqr.javafixture.domain.Person
...
net.jqwik.api.TooManyFilterMissesException: Filtering [net.jqwik.api.RandomGenerator$$Lambda$634/0x0000000800df30e0@9a9aa68] missed more than 10000 times.
- Lack of documentations for
fixture monkeycomponents:- Some of the useful components such
as
InnerSpec,ArbitraryExpression,ExpressionGeneratormay be powerful, but the lack of documentations make it difficult to implement and make use of
- Some of the useful components such
as
- Fixture Monkey is heavily reliant on
Jqwikin the creation ofArbitraryJqwikis a property-based testing framework for java- Here is a good article that summarize the key features
of
jqwik: https://www.baeldung.com/java-jqwik-property-based-testing - This means that apart from generating of mock data (with
Arbitrary+Fixture Monkey), we are also able to make use ofjqwikto perform property-based tests which is a complement to what we already set up for mocking of data
- Complexities in simple use cases
- In order to override a particular
- Flexibility in independent field-level constraints
- The support for Bean Validations (
javax,jakarta),jqwikas well as self-made annotations provide much flexibility for having custom logic in generating data in fields - However, this can only be done on the class itself, where cases if we do not want to "dirty"
the POJO (e.g.: to segregate mock data constraints and actual domain constraints), we will
need to duplicate the class itself OR to go for another approach for custom generation
- Another approach would be through the use of
Arbitrarywhich can result in too much boilerplate codes & steeper learning curve
- Another approach would be through the use of
- The support for Bean Validations (