From d0020c33a1372178d06bd4b0e0fdab5d2b45b094 Mon Sep 17 00:00:00 2001 From: Arman Sharif Date: Thu, 26 Jan 2023 16:29:47 -0800 Subject: [PATCH] BAEL-6164: Generate unit test data with Instancio (#13350) --- testing-modules/instancio/.gitignore | 14 ++ testing-modules/instancio/README.md | 1 + testing-modules/instancio/pom.xml | 58 ++++++ .../instancio/abstracttype/AbstractItem.java | 6 + .../com/baeldung/instancio/generics/Item.java | 18 ++ .../com/baeldung/instancio/generics/Pair.java | 20 ++ .../baeldung/instancio/generics/Triplet.java | 24 +++ .../instancio/student/model/Address.java | 31 ++++ .../instancio/student/model/ContactInfo.java | 28 +++ .../instancio/student/model/Course.java | 53 ++++++ .../student/model/EmergencyContact.java | 14 ++ .../instancio/student/model/Grade.java | 5 + .../instancio/student/model/Phone.java | 23 +++ .../instancio/student/model/Student.java | 57 ++++++ .../student/service/CourseService.java | 11 ++ .../student/service/EnrollmentException.java | 8 + .../student/service/EnrollmentService.java | 30 +++ .../instancio/util/PrettyToString.java | 21 +++ .../basics/CreateStudentUnitTest.java | 174 ++++++++++++++++++ .../UsingCustomGeneratorUnitTest.java | 57 ++++++ .../CreatingGenericTypesUnitTest.java | 100 ++++++++++ .../mockito/InstancioWithMockitoUnitTest.java | 45 +++++ .../ReproducingTestFailureUnitTest.java | 60 ++++++ .../selectors/SelectorScopesUnitTest.java | 76 ++++++++ .../selectors/SelectorsUnitTest.java | 104 +++++++++++ .../settings/CustomSettingsUnitTest.java | 67 +++++++ .../UsingInstancioPropertiesUnitTest.java | 50 +++++ .../instancio/subtype/SubtypeUnitTest.java | 54 ++++++ .../src/test/resources/instancio.properties | 2 + testing-modules/pom.xml | 1 + 30 files changed, 1212 insertions(+) create mode 100644 testing-modules/instancio/.gitignore create mode 100644 testing-modules/instancio/README.md create mode 100644 testing-modules/instancio/pom.xml create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/abstracttype/AbstractItem.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Item.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Pair.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Triplet.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Address.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/ContactInfo.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Course.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/EmergencyContact.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Grade.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Phone.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Student.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/CourseService.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/EnrollmentException.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/EnrollmentService.java create mode 100644 testing-modules/instancio/src/main/java/com/baeldung/instancio/util/PrettyToString.java create mode 100644 testing-modules/instancio/src/test/java/com/baeldung/instancio/basics/CreateStudentUnitTest.java create mode 100644 testing-modules/instancio/src/test/java/com/baeldung/instancio/generators/UsingCustomGeneratorUnitTest.java create mode 100644 testing-modules/instancio/src/test/java/com/baeldung/instancio/generics/CreatingGenericTypesUnitTest.java create mode 100644 testing-modules/instancio/src/test/java/com/baeldung/instancio/mockito/InstancioWithMockitoUnitTest.java create mode 100644 testing-modules/instancio/src/test/java/com/baeldung/instancio/reproducing/ReproducingTestFailureUnitTest.java create mode 100644 testing-modules/instancio/src/test/java/com/baeldung/instancio/selectors/SelectorScopesUnitTest.java create mode 100644 testing-modules/instancio/src/test/java/com/baeldung/instancio/selectors/SelectorsUnitTest.java create mode 100644 testing-modules/instancio/src/test/java/com/baeldung/instancio/settings/CustomSettingsUnitTest.java create mode 100644 testing-modules/instancio/src/test/java/com/baeldung/instancio/settings/UsingInstancioPropertiesUnitTest.java create mode 100644 testing-modules/instancio/src/test/java/com/baeldung/instancio/subtype/SubtypeUnitTest.java create mode 100644 testing-modules/instancio/src/test/resources/instancio.properties diff --git a/testing-modules/instancio/.gitignore b/testing-modules/instancio/.gitignore new file mode 100644 index 0000000000..7f300600e6 --- /dev/null +++ b/testing-modules/instancio/.gitignore @@ -0,0 +1,14 @@ +*.class + +.settings +.project + +#folders# +/target +/src/main/webapp/WEB-INF/classes +*/META-INF/* + +# Packaged files # +*.jar +*.war +*.ear diff --git a/testing-modules/instancio/README.md b/testing-modules/instancio/README.md new file mode 100644 index 0000000000..881477f036 --- /dev/null +++ b/testing-modules/instancio/README.md @@ -0,0 +1 @@ +### Relevant articles diff --git a/testing-modules/instancio/pom.xml b/testing-modules/instancio/pom.xml new file mode 100644 index 0000000000..137da91897 --- /dev/null +++ b/testing-modules/instancio/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + instancio + instancio + jar + + + com.baeldung + testing-modules + 1.0.0-SNAPSHOT + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + + org.instancio + instancio-junit + ${instancio.version} + test + + + org.assertj + assertj-core + ${assertj.version} + + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + + + + + 2.6.0 + 2.14.1 + 5.9.2 + + \ No newline at end of file diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/abstracttype/AbstractItem.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/abstracttype/AbstractItem.java new file mode 100644 index 0000000000..4dd21e6ffb --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/abstracttype/AbstractItem.java @@ -0,0 +1,6 @@ +package com.baeldung.instancio.abstracttype; + +public interface AbstractItem { + + T getValue(); +} diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Item.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Item.java new file mode 100644 index 0000000000..19450a936c --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Item.java @@ -0,0 +1,18 @@ +package com.baeldung.instancio.generics; + +import com.baeldung.instancio.abstracttype.AbstractItem; + +public class Item implements AbstractItem { + + private T value; + + @Override + public T getValue() { + return value; + } + + @Override + public String toString() { + return String.format("Item[value=%s]", value); + } +} \ No newline at end of file diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Pair.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Pair.java new file mode 100644 index 0000000000..48b43fe9aa --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Pair.java @@ -0,0 +1,20 @@ +package com.baeldung.instancio.generics; + +public class Pair { + + private L left; + private R right; + + public L getLeft() { + return left; + } + + public R getRight() { + return right; + } + + @Override + public String toString() { + return String.format("Pair[left=%s, right=%s]", left, right); + } +} \ No newline at end of file diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Triplet.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Triplet.java new file mode 100644 index 0000000000..528c4d7c98 --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/generics/Triplet.java @@ -0,0 +1,24 @@ +package com.baeldung.instancio.generics; + +public class Triplet { + private L left; + private M middle; + private R right; + + public L getLeft() { + return left; + } + + public M getMiddle() { + return middle; + } + + public R getRight() { + return right; + } + + @Override + public String toString() { + return String.format("Triplet[left=%s, middle=%s, right=%s]", left, right, middle); + } +} \ No newline at end of file diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Address.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Address.java new file mode 100644 index 0000000000..cda34690e5 --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Address.java @@ -0,0 +1,31 @@ +package com.baeldung.instancio.student.model; + +import com.baeldung.instancio.util.PrettyToString; + +public class Address { + private String street; + private String city; + private String country; + + public String getStreet() { + return street; + } + + public String getCity() { + return city; + } + + public String getCountry() { + return country; + } + + public void setCountry(final String country) { + this.country = country; + } + + @Override + public String toString() { + return PrettyToString.toPrettyString(this); + } + +} diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/ContactInfo.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/ContactInfo.java new file mode 100644 index 0000000000..71534192c6 --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/ContactInfo.java @@ -0,0 +1,28 @@ +package com.baeldung.instancio.student.model; + +import com.baeldung.instancio.util.PrettyToString; + +import java.util.List; + +public class ContactInfo { + private Address address; + private List phones; + private String email; + + public Address getAddress() { + return address; + } + + public List getPhones() { + return phones; + } + + public String getEmail() { + return email; + } + + @Override + public String toString() { + return PrettyToString.toPrettyString(this); + } +} diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Course.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Course.java new file mode 100644 index 0000000000..543f6eaf4e --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Course.java @@ -0,0 +1,53 @@ +package com.baeldung.instancio.student.model; + +import com.baeldung.instancio.util.PrettyToString; + +import java.time.Duration; +import java.time.LocalDate; +import java.util.Objects; + +public class Course { + private String title; + private String code; + private LocalDate startDate; + private Duration duration; + private String instructor; + + public String getTitle() { + return title; + } + + public String getCode() { + return code; + } + + public LocalDate getStartDate() { + return startDate; + } + + public Duration getDuration() { + return duration; + } + + public String getInstructor() { + return instructor; + } + + @Override + public String toString() { + return PrettyToString.toPrettyString(this); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof Course)) return false; + final Course course = (Course) o; + return Objects.equals(getCode(), course.getCode()); + } + + @Override + public int hashCode() { + return Objects.hash(getCode()); + } +} diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/EmergencyContact.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/EmergencyContact.java new file mode 100644 index 0000000000..7c5ce21738 --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/EmergencyContact.java @@ -0,0 +1,14 @@ +package com.baeldung.instancio.student.model; + +public class EmergencyContact { + private String name; + private Phone phone; + + public String getName() { + return name; + } + + public Phone getPhone() { + return phone; + } +} diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Grade.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Grade.java new file mode 100644 index 0000000000..8bd9341a18 --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Grade.java @@ -0,0 +1,5 @@ +package com.baeldung.instancio.student.model; + +public enum Grade { + A, B, C, D, F +} diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Phone.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Phone.java new file mode 100644 index 0000000000..887cce5c7d --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Phone.java @@ -0,0 +1,23 @@ +package com.baeldung.instancio.student.model; + +import com.baeldung.instancio.util.PrettyToString; + +public class Phone { + + private String countryCode; + private String number; + + public String getCountryCode() { + return countryCode; + } + + public String getNumber() { + return number; + } + + @Override + public String toString() { + return PrettyToString.toPrettyString(this); + } + +} diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Student.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Student.java new file mode 100644 index 0000000000..fd6b3cbc1c --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/model/Student.java @@ -0,0 +1,57 @@ +package com.baeldung.instancio.student.model; + + +import com.baeldung.instancio.util.PrettyToString; + +import java.time.LocalDate; +import java.time.Year; +import java.util.Map; +import java.util.UUID; + +public class Student { + private UUID id; + private String firstName; + private String lastName; + private LocalDate dateOfBirth; + private ContactInfo contactInfo; + private EmergencyContact emergencyContact; + private Year enrollmentYear; + private Map courseGrades; + + public UUID getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public LocalDate getDateOfBirth() { + return dateOfBirth; + } + + public ContactInfo getContactInfo() { + return contactInfo; + } + + public EmergencyContact getEmergencyContact() { + return emergencyContact; + } + + public Year getEnrollmentYear() { + return enrollmentYear; + } + + public Map getCourseGrades() { + return courseGrades; + } + + @Override + public String toString() { + return PrettyToString.toPrettyString(this); + } +} diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/CourseService.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/CourseService.java new file mode 100644 index 0000000000..e71da0b23e --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/CourseService.java @@ -0,0 +1,11 @@ +package com.baeldung.instancio.student.service; + +import com.baeldung.instancio.student.model.Course; + +public class CourseService { + + public Course getByCode(String courseCode) { + throw new UnsupportedOperationException( + "This class should be mocked. Persistence is not available in a unit test"); + } +} diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/EnrollmentException.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/EnrollmentException.java new file mode 100644 index 0000000000..2398556b73 --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/EnrollmentException.java @@ -0,0 +1,8 @@ +package com.baeldung.instancio.student.service; + +public class EnrollmentException extends RuntimeException { + + public EnrollmentException(final String message) { + super(message); + } +} diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/EnrollmentService.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/EnrollmentService.java new file mode 100644 index 0000000000..d505a5881c --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/student/service/EnrollmentService.java @@ -0,0 +1,30 @@ +package com.baeldung.instancio.student.service; + +import com.baeldung.instancio.student.model.Course; +import com.baeldung.instancio.student.model.Grade; +import com.baeldung.instancio.student.model.Student; + +import java.util.Collection; + +public class EnrollmentService { + + private CourseService courseService; + + public boolean enrollStudent(Student student, Course course) { + Collection grades = student.getCourseGrades().values(); + if (grades.contains(Grade.F)) { + throw new EnrollmentException(String.format("Student %s has at least 1 failed course", student.getId())); + } + // process enrollment... + return true; + } + + public boolean enrollStudent(Student student, String courseCode) { + Course course = courseService.getByCode(courseCode); + if (course == null) { + throw new EnrollmentException("Course not found: " + courseCode); + } + return enrollStudent(student, course); + } + +} diff --git a/testing-modules/instancio/src/main/java/com/baeldung/instancio/util/PrettyToString.java b/testing-modules/instancio/src/main/java/com/baeldung/instancio/util/PrettyToString.java new file mode 100644 index 0000000000..163f3673c7 --- /dev/null +++ b/testing-modules/instancio/src/main/java/com/baeldung/instancio/util/PrettyToString.java @@ -0,0 +1,21 @@ +package com.baeldung.instancio.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +public class PrettyToString { + + private static final ObjectWriter objectWriter = new ObjectMapper() + .registerModules(new JavaTimeModule()) + .writerWithDefaultPrettyPrinter(); + + public static String toPrettyString(Object obj) { + try { + return objectWriter.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/testing-modules/instancio/src/test/java/com/baeldung/instancio/basics/CreateStudentUnitTest.java b/testing-modules/instancio/src/test/java/com/baeldung/instancio/basics/CreateStudentUnitTest.java new file mode 100644 index 0000000000..524c8dbc91 --- /dev/null +++ b/testing-modules/instancio/src/test/java/com/baeldung/instancio/basics/CreateStudentUnitTest.java @@ -0,0 +1,174 @@ +package com.baeldung.instancio.basics; + +import com.baeldung.instancio.student.model.Address; +import com.baeldung.instancio.student.model.ContactInfo; +import com.baeldung.instancio.student.model.Course; +import com.baeldung.instancio.student.model.Grade; +import com.baeldung.instancio.student.model.Phone; +import com.baeldung.instancio.student.model.Student; +import org.instancio.Instancio; +import org.instancio.Model; +import org.instancio.junit.InstancioExtension; +import org.instancio.junit.InstancioSource; +import org.instancio.junit.Seed; +import org.instancio.junit.WithSettings; +import org.instancio.settings.Keys; +import org.instancio.settings.Settings; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; + +import java.time.LocalDate; +import java.time.Year; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.instancio.Select.all; +import static org.instancio.Select.field; + +/** + * Sample test class using Instancio to generate test objects. + * + *

Note: using {@link InstancioExtension} is optional. The extension adds support for: + *

+ * - reporting seed value if a test fails + * - {@link Seed} annotation for reproducing failed tests + * - {@link WithSettings} for injecting custom settings, if needed + */ +@ExtendWith(InstancioExtension.class) +class CreateStudentUnitTest { + + /** + * Common settings to be used by all test methods. + */ + @WithSettings + private static final Settings settings = Settings.create() + .set(Keys.COLLECTION_MAX_SIZE, 3); + + /** + * A {@link Model} is a template for creating objects. + * Objects created from a model can be created as is, or customized, if needed. + */ + private static Model studentModel() { + return Instancio.of(Student.class) + .generate(field(Student::getDateOfBirth), gen -> gen.temporal().localDate().past()) + .generate(field(Student::getEnrollmentYear), gen -> gen.temporal().year().past()) + .generate(field(ContactInfo::getEmail), gen -> gen.text().pattern("#a#a#a#a#a#a@example.com")) + .generate(field(Phone::getCountryCode), gen -> gen.string().prefix("+").digits().maxLength(2)) + .withNullable(field(Student::getEmergencyContact)) + .toModel(); + } + + private static void assertModelProperties(Student student) { + assertThat(student.getDateOfBirth()).isBefore(LocalDate.now()); + assertThat(student.getEnrollmentYear()).isLessThan(Year.now()); + assertThat(student.getContactInfo().getEmail()).matches("^[a-zA-Z0-9]+@example.com$"); + assertThat(student.getContactInfo().getPhones()) + .extracting(Phone::getCountryCode) + .allSatisfy(countryCode -> assertThat(countryCode).matches("^\\+\\d\\d?$")); + } + + /** + * Generates random Student objects based on the Model. + */ + @Test + void whenGivenAModel_thenShouldCreateAStudentBasedOnModel() { + Student student = Instancio.create(studentModel()); + + assertModelProperties(student); + } + + /** + * Generate a list of international students based on the Model. + */ + @Test + void whenGivenAModel_thenShouldCreateAListOfStudents() { + // Given + final int numberOfStudents = 100; + final List countries = Arrays.asList( + "China", "Germany", "India", "Poland", "Romania", "Sweden", "Switzerland"); + + // When + List studentList = Instancio.ofList(studentModel()) + .size(numberOfStudents) + .generate(field(Address::getCountry), gen -> gen.oneOf(countries)) + .create(); + + // Then + assertThat(studentList).hasSize(numberOfStudents) + .allSatisfy(CreateStudentUnitTest::assertModelProperties) + .extracting(student -> student.getContactInfo().getAddress().getCountry()) + .allSatisfy(country -> assertThat(country).isIn(countries)); + } + + /** + * Use the Model to create a student with a failed course. + * This test also demonstrates how Instancio can provide + * arguments to parameterized tests. + * + * @param failedCourse provided by Instancio + */ + @InstancioSource + @ParameterizedTest + void whenGivenFailingGrade_thenStudentShouldHaveAFailedCourse(final Course failedCourse) { + // Given + final Model model = studentModel(); + final Grade failingGrade = Grade.F; + + // When + Student student = Instancio.of(model) + .generate(field(Student::getCourseGrades), gen -> gen.map().with(failedCourse, failingGrade)) + .create(); + + // Then + Map courseGrades = student.getCourseGrades(); + assertModelProperties(student); + assertThat(courseGrades).containsEntry(failedCourse, failingGrade); + } + + /** + * Generate a student with only Grades A and/or B. + */ + @Test + void whenGivenGoodGrades_thenCreatedStudentShouldHaveExpectedGrades() { + // Given + final int numOfCourses = 10; + final Grade[] grades = {Grade.A, Grade.B}; + + // When + Student student = Instancio.of(studentModel()) + .generate(all(Grade.class), gen -> gen.oneOf(grades)) + .generate(field(Student::getCourseGrades), gen -> gen.map().size(numOfCourses)) + .create(); + + // Then + Map courseGrades = student.getCourseGrades(); + assertModelProperties(student); + assertThat(courseGrades.values()) + .hasSize(numOfCourses) + .containsAnyOf(grades) + .doesNotContain(Grade.C, Grade.D, Grade.F); + } + + /** + * Generate String fields prefixed with the field's name. + */ + @Test + void whenGivenCustomSettings_thenStudentShouldBeCreatedUsingTheSettings() { + // Given + Settings customSettings = Settings.create() + .set(Keys.STRING_FIELD_PREFIX_ENABLED, true); + + // When + Student student = Instancio.of(studentModel()) + .withSettings(customSettings) + .create(); + + // Then + assertThat(student.getFirstName()).startsWith("firstName_"); + assertThat(student.getLastName()).startsWith("lastName_"); + assertThat(student.getContactInfo().getAddress().getCity()).startsWith("city_"); + } +} diff --git a/testing-modules/instancio/src/test/java/com/baeldung/instancio/generators/UsingCustomGeneratorUnitTest.java b/testing-modules/instancio/src/test/java/com/baeldung/instancio/generators/UsingCustomGeneratorUnitTest.java new file mode 100644 index 0000000000..8e5643baec --- /dev/null +++ b/testing-modules/instancio/src/test/java/com/baeldung/instancio/generators/UsingCustomGeneratorUnitTest.java @@ -0,0 +1,57 @@ +package com.baeldung.instancio.generators; + +import com.baeldung.instancio.student.model.Address; +import com.baeldung.instancio.student.model.ContactInfo; +import com.baeldung.instancio.student.model.Phone; +import com.baeldung.instancio.student.model.Student; +import org.instancio.Instancio; +import org.instancio.Random; +import org.instancio.generator.AfterGenerate; +import org.instancio.generator.Generator; +import org.instancio.generator.Hints; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.instancio.Select.all; +import static org.instancio.Select.field; + +class UsingCustomGeneratorUnitTest { + private static final String ARGENTINA = "Argentina"; + private static final int PHONES_SIZE = 5; + + private static final Generator

ADDRESS_GENERATOR = new Generator
() { + @Override + public Address generate(final Random random) { + Address address = new Address(); + address.setCountry(ARGENTINA); + return address; + } + + @Override + public Hints hints() { + // The hint telling the engine to populate any field that has a null value + return Hints.afterGenerate(AfterGenerate.POPULATE_NULLS); + } + }; + + @Test + void whenGivenAGenerator_objectShouldBeCreatedUsingCustomGenerator() { + Student student = Instancio.of(Student.class) + .supply(all(Address.class), ADDRESS_GENERATOR) + .generate(field(ContactInfo::getPhones), gen -> gen.collection().size(PHONES_SIZE)) + .create(); + + ContactInfo contactInfo = student.getContactInfo(); + Address address = contactInfo.getAddress(); + List phones = contactInfo.getPhones(); + + assertThat(phones).hasSize(PHONES_SIZE); + assertThat(address.getCountry()).isEqualTo(ARGENTINA); + // null fields were populated with random values + assertThat(address.getStreet()).isNotNull(); + assertThat(address.getCity()).isNotNull(); + } + +} diff --git a/testing-modules/instancio/src/test/java/com/baeldung/instancio/generics/CreatingGenericTypesUnitTest.java b/testing-modules/instancio/src/test/java/com/baeldung/instancio/generics/CreatingGenericTypesUnitTest.java new file mode 100644 index 0000000000..4ed22abeaf --- /dev/null +++ b/testing-modules/instancio/src/test/java/com/baeldung/instancio/generics/CreatingGenericTypesUnitTest.java @@ -0,0 +1,100 @@ +package com.baeldung.instancio.generics; + +import com.baeldung.instancio.student.model.Address; +import org.instancio.Instancio; +import org.instancio.TypeToken; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.instancio.Select.allLongs; +import static org.instancio.Select.allStrings; + +/** + * Examples of creating generic types using {@link TypeToken}. + */ +class CreatingGenericTypesUnitTest { + + + @Test + void whenGivenTypeToken_shouldCreateItem() { + Item item = Instancio.create(new TypeToken>() {}); + + assertThat(item.getValue()).isNotBlank(); + } + + @Test + void whenGivenTypeToken_shouldCreateCustomizedItem() { + Pair pair = Instancio.of(new TypeToken>() {}) + .generate(allStrings(), gen -> gen.oneOf("foo", "bar")) + .generate(allLongs(), gen -> gen.longs().range(5L, 10L)) + .create(); + + assertThat(pair.getLeft()).isIn("foo", "bar"); + assertThat(pair.getRight()).isBetween(5L, 10L); + } + + @Test + void whenGivenTypeToken_shouldCreateTriplet() { + Triplet triplet = Instancio.create(new TypeToken>() {}); + + assertThat(triplet.getLeft()).isNotBlank(); + assertThat(triplet.getMiddle()).isNotNull(); + assertThat(triplet.getRight()).isNotNull(); + } + + @Test + void whenGivenTypeToken_shouldCreateCollection() { + List list = Instancio.create(new TypeToken>() {}); + + assertThat(list).isNotEmpty().doesNotContainNull(); + } + + @Test + void whenGivenTypeToken_shouldCreateMap() { + Map map = Instancio.create(new TypeToken>() {}); + + assertThat(map).isNotEmpty(); + } + + /** + * Using type token to create more complex generic objects. + */ + @Test + void whenGivenTypeTokenWithNestGenerics_shouldCreateAnInstanceOfSpecifiedType() { + List>> list = Instancio.create( + new TypeToken>>>() {}); + + assertThat(list) + .isNotEmpty() + .allSatisfy(triplet -> { + assertThat(triplet.getLeft()).isInstanceOf(Integer.class); + assertThat(triplet.getMiddle()).isInstanceOf(LocalDate.class); + assertThat(triplet.getRight()) + .isInstanceOf(Item.class) + .satisfies(item -> assertThat(item.getValue()).isNotBlank()); + }); + + // Sample output + list.forEach(System.out::println); + } + + + /** + * Alternative way to create generic objects is using 'withTypeParameters'. + * However, this approach generates an "unchecked assignment" warning. + */ + @Test + @SuppressWarnings("unchecked") + void whenGivenClassWithTypeParameters_shouldCreateGenericType() { + Map map = Instancio.of(Map.class) + .withTypeParameters(UUID.class, Address.class) + .create(); + + assertThat(map).isNotEmpty(); + } +} diff --git a/testing-modules/instancio/src/test/java/com/baeldung/instancio/mockito/InstancioWithMockitoUnitTest.java b/testing-modules/instancio/src/test/java/com/baeldung/instancio/mockito/InstancioWithMockitoUnitTest.java new file mode 100644 index 0000000000..1382589a84 --- /dev/null +++ b/testing-modules/instancio/src/test/java/com/baeldung/instancio/mockito/InstancioWithMockitoUnitTest.java @@ -0,0 +1,45 @@ +package com.baeldung.instancio.mockito; + +import com.baeldung.instancio.student.model.Course; +import com.baeldung.instancio.student.model.Grade; +import com.baeldung.instancio.student.model.Student; +import com.baeldung.instancio.student.service.CourseService; +import com.baeldung.instancio.student.service.EnrollmentService; +import org.instancio.Instancio; +import org.instancio.junit.InstancioExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.instancio.Select.all; +import static org.mockito.Mockito.when; + +@ExtendWith({MockitoExtension.class, InstancioExtension.class}) +class InstancioWithMockitoUnitTest { + + @Mock + private CourseService courseService; + + @InjectMocks + private EnrollmentService enrollmentService; + + @Test + void givenStudentWithoutGradeF_thenShouldEnrollStudentInCourse() { + // Given + Student student = Instancio.of(Student.class) + .generate(all(Grade.class), gen -> gen.enumOf(Grade.class).excluding(Grade.F)) + .create(); + + Course course = Instancio.create(Course.class); + when(courseService.getByCode(course.getCode())).thenReturn(course); + + // When + boolean isEnrolled = enrollmentService.enrollStudent(student, course.getCode()); + + // Then + assertThat(isEnrolled).isTrue(); + } +} diff --git a/testing-modules/instancio/src/test/java/com/baeldung/instancio/reproducing/ReproducingTestFailureUnitTest.java b/testing-modules/instancio/src/test/java/com/baeldung/instancio/reproducing/ReproducingTestFailureUnitTest.java new file mode 100644 index 0000000000..0d6c8cfa9a --- /dev/null +++ b/testing-modules/instancio/src/test/java/com/baeldung/instancio/reproducing/ReproducingTestFailureUnitTest.java @@ -0,0 +1,60 @@ +package com.baeldung.instancio.reproducing; + +import com.baeldung.instancio.student.model.Course; +import com.baeldung.instancio.student.model.Student; +import com.baeldung.instancio.student.service.EnrollmentService; +import org.instancio.Instancio; +import org.instancio.junit.InstancioExtension; +import org.instancio.junit.Seed; +import org.instancio.junit.WithSettings; +import org.instancio.settings.Keys; +import org.instancio.settings.Settings; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(InstancioExtension.class) +class ReproducingTestFailureUnitTest { + + @WithSettings + private static final Settings settings = Settings.create() + .set(Keys.COLLECTION_MIN_SIZE, 50) + .set(Keys.COLLECTION_MAX_SIZE, 100) + .lock(); + + private final EnrollmentService enrollmentService = new EnrollmentService(); + + /** + * This test fails because the {@code enrollInCourse()} method + * throws an exception if the student has at least one grade F. + * + *

Sample error message generated by {@link InstancioExtension}: + * + *

+     * timestamp = 2023-01-24T13:50:12.436704221, Instancio = Test method 'enrollStudent' failed with seed: 1234
+     * 
+ *

+ * Using the reported seed value we can reproduce the test failure. + */ + @Test + @Seed(1234) + @Disabled("This test fails on purpose to demonstrate failure reporting by InstancioExtension") + void whenGivenNoFailingGrades_thenShouldEnrollStudentInCourse() { + // Given + Course course = Instancio.create(Course.class); + Student student = Instancio.of(Student.class) + // The test can be fixed by uncommenting the line below: + //.generate(all(Grade.class), gen -> gen.enumOf(Grade.class).excluding(Grade.F)) + .create(); + + System.out.println(student); // same data generated on each run + + // When + boolean isEnrolled = enrollmentService.enrollStudent(student, course); + + // Then + assertThat(isEnrolled).isTrue(); + } +} diff --git a/testing-modules/instancio/src/test/java/com/baeldung/instancio/selectors/SelectorScopesUnitTest.java b/testing-modules/instancio/src/test/java/com/baeldung/instancio/selectors/SelectorScopesUnitTest.java new file mode 100644 index 0000000000..683b510f0c --- /dev/null +++ b/testing-modules/instancio/src/test/java/com/baeldung/instancio/selectors/SelectorScopesUnitTest.java @@ -0,0 +1,76 @@ +package com.baeldung.instancio.selectors; + +import com.baeldung.instancio.student.model.ContactInfo; +import com.baeldung.instancio.student.model.EmergencyContact; +import com.baeldung.instancio.student.model.Phone; +import com.baeldung.instancio.student.model.Student; +import org.instancio.Instancio; +import org.instancio.Scope; +import org.instancio.Select; +import org.instancio.TargetSelector; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.instancio.Select.allStrings; +import static org.instancio.Select.scope; + +/** + * Examples of various selector {@link Scope}. + * Scopes allow narrowing down selector targets. + */ +class SelectorScopesUnitTest { + + /** + * Prefix all String fields in Phone class with "phone_". + */ + @Test + void whenGivenClassScope_shouldSelectTargetsWithinClass() { + // Given + final String prefix = "phone_"; + final Scope phoneClass = scope(Phone.class); + + // When + Student student = Instancio.of(Student.class) + .generate(allStrings().within(phoneClass), gen -> gen.string().prefix(prefix)) + .create(); + + // Then + + // matches phone numbers + Phone emergencyContactPhone = student.getEmergencyContact().getPhone(); + assertThat(emergencyContactPhone.getCountryCode()).startsWith(prefix); + assertThat(emergencyContactPhone.getNumber()).startsWith(prefix); + assertThat(student.getContactInfo().getPhones()).allSatisfy(phone -> { + assertThat(phone.getCountryCode()).startsWith(prefix); + assertThat(phone.getNumber()).startsWith(prefix); + }); + + // does not match other fields + assertThat(student.getContactInfo().getAddress().getCity()).doesNotStartWith(prefix); + } + + /** + * Using scope to set student's and their emergency contact's + * phone number to different values. + */ + @Test + void whenGivenFieldScope_shouldSelectTargetsWithinField() { + // Given + TargetSelector studentPhone = Select.field(Phone::getNumber).within(scope(ContactInfo.class)); + TargetSelector emergencyPhone = Select.field(Phone::getNumber).within(scope(EmergencyContact.class)); + + // When + Student student = Instancio.of(Student.class) + .set(studentPhone, "student") + .set(emergencyPhone, "emergency") + .create(); + + // Then + assertThat(student.getContactInfo().getPhones()) + .isNotEmpty() + .allSatisfy(phone -> assertThat(phone.getNumber()).isEqualTo("student")); + + assertThat(student.getEmergencyContact().getPhone().getNumber()) + .isEqualTo("emergency"); + } +} diff --git a/testing-modules/instancio/src/test/java/com/baeldung/instancio/selectors/SelectorsUnitTest.java b/testing-modules/instancio/src/test/java/com/baeldung/instancio/selectors/SelectorsUnitTest.java new file mode 100644 index 0000000000..fe6b9cc3f4 --- /dev/null +++ b/testing-modules/instancio/src/test/java/com/baeldung/instancio/selectors/SelectorsUnitTest.java @@ -0,0 +1,104 @@ +package com.baeldung.instancio.selectors; + +import com.baeldung.instancio.student.model.Address; +import com.baeldung.instancio.student.model.ContactInfo; +import com.baeldung.instancio.student.model.Phone; +import com.baeldung.instancio.student.model.Student; +import org.instancio.FieldSelectorBuilder; +import org.instancio.Instancio; +import org.instancio.Select; +import org.instancio.Selector; +import org.instancio.SelectorGroup; +import org.instancio.TargetSelector; +import org.junit.jupiter.api.Test; + +import java.util.Collection; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.instancio.Select.all; +import static org.instancio.Select.field; +import static org.instancio.Select.fields; +import static org.instancio.Select.types; + +/** + * Examples of various types of selectors provided by the {@link Select} class. + */ +class SelectorsUnitTest { + + @Test + void whenGivenFieldSelector_shouldCustomizeSelectedField() { + Address address = Instancio.of(Address.class) + .set(field(Address::getCity), "London") + .set(field(Address.class, "country"), "UK") + .create(); + + assertThat(address.getCity()).isEqualTo("London"); + assertThat(address.getCountry()).isEqualTo("UK"); + } + + @Test + void whenGivenClassSelector_shouldCustomizeSelectedClass() { + // Given + final Selector allStrings = all(String.class); + final String prefix = "test_"; + + // When + Address address = Instancio.of(Address.class) + .generate(allStrings, gen -> gen.string().prefix(prefix)) + .create(); + + // Then + assertThat(address.getCity()).startsWith(prefix); + assertThat(address.getCountry()).startsWith(prefix); + } + + @Test + void whenGivenPredicateFieldSelector_shouldCustomizeMatchingFields() { + // Given: regie matching 'city' and 'country' fields + final FieldSelectorBuilder fieldsMatchingRegex = fields().matching("c.*y"); + + // When + Address address = Instancio.of(Address.class) + .ignore(fieldsMatchingRegex) + .create(); + + // Then + assertThat(address.getCity()).isNull(); + assertThat(address.getCountry()).isNull(); + assertThat(address.getStreet()).isNotBlank(); + } + + @Test + void whenGivenPredicateClassSelector_shouldCustomizeMatchingClasses() { + // Given + final TargetSelector allTypesOfCollections = types().of(Collection.class); + final int size = 3; + + // When + ContactInfo contactInfo = Instancio.of(ContactInfo.class) + .generate(allTypesOfCollections, gen -> gen.collection().size(size)) + .create(); + + // Then + List phones = contactInfo.getPhones(); + assertThat(phones).hasSize(size); + } + + @Test + void whenGivenSelectorGroup_shouldCustomizeSelectedFields() { + // Given + SelectorGroup ignoredFields = all( + field(Student::getId), + field(Student::getDateOfBirth)); + + // When + Student student = Instancio.of(Student.class) + .ignore(ignoredFields) + .create(); + + // Then + assertThat(student.getId()).isNull(); + assertThat(student.getDateOfBirth()).isNull(); + } +} diff --git a/testing-modules/instancio/src/test/java/com/baeldung/instancio/settings/CustomSettingsUnitTest.java b/testing-modules/instancio/src/test/java/com/baeldung/instancio/settings/CustomSettingsUnitTest.java new file mode 100644 index 0000000000..b9b8b4ac6d --- /dev/null +++ b/testing-modules/instancio/src/test/java/com/baeldung/instancio/settings/CustomSettingsUnitTest.java @@ -0,0 +1,67 @@ +package com.baeldung.instancio.settings; + +import com.baeldung.instancio.student.model.ContactInfo; +import com.baeldung.instancio.student.model.Phone; +import org.instancio.Instancio; +import org.instancio.junit.InstancioExtension; +import org.instancio.junit.WithSettings; +import org.instancio.settings.Keys; +import org.instancio.settings.Settings; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link InstancioExtension} allows injecting custom settings + * using the {@link WithSettings} annotation. + */ +@ExtendWith(InstancioExtension.class) +class CustomSettingsUnitTest { + + private static final int MIN_SIZE = 0; + private static final int MAX_SIZE = 3; + + /** + * Common settings to be used by all test methods. + */ + @WithSettings + private static final Settings settings = Settings.create() + .set(Keys.COLLECTION_MIN_SIZE, MIN_SIZE) + .set(Keys.COLLECTION_MAX_SIZE, MAX_SIZE) + .lock(); + + + @Test + void whenGivenInjectedSettings_shouldUseCustomSettings() { + ContactInfo info = Instancio.create(ContactInfo.class); + + List phones = info.getPhones(); + assertThat(phones).hasSizeBetween(MIN_SIZE, MAX_SIZE); + } + + @Test + void whenSettingsOverridden_shouldUseTheOverrides() { + // Given + final int collectionSize = 50; + Settings additionalSettings = Settings.create() + .set(Keys.STRING_FIELD_PREFIX_ENABLED, true) + .set(Keys.COLLECTION_MIN_SIZE, collectionSize) + .set(Keys.COLLECTION_MAX_SIZE, collectionSize); + + // When + ContactInfo info = Instancio.of(ContactInfo.class) + .withSettings(additionalSettings) + .create(); + + // Then + assertThat(info.getPhones()) + .hasSize(collectionSize) + .allSatisfy(phone -> { + assertThat(phone.getCountryCode()).startsWith("countryCode_"); + assertThat(phone.getNumber()).startsWith("number_"); + }); + } +} diff --git a/testing-modules/instancio/src/test/java/com/baeldung/instancio/settings/UsingInstancioPropertiesUnitTest.java b/testing-modules/instancio/src/test/java/com/baeldung/instancio/settings/UsingInstancioPropertiesUnitTest.java new file mode 100644 index 0000000000..6a14d8dc88 --- /dev/null +++ b/testing-modules/instancio/src/test/java/com/baeldung/instancio/settings/UsingInstancioPropertiesUnitTest.java @@ -0,0 +1,50 @@ +package com.baeldung.instancio.settings; + +import org.instancio.Instancio; +import org.instancio.settings.Keys; +import org.instancio.settings.Settings; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class UsingInstancioPropertiesUnitTest { + + /** + * Instancio automatically loads {@code instancio.properties} file, + * if it's present, from the root of the classpath. + * + *

Float range was overridden to [0, 100]. + * See: {@code src/test/resources/instancio.properties} + */ + @Test + void whenInstancioPropertiesAreOnClasspath_shouldUseConfiguredProperties() { + Set floats = Instancio.ofSet(Float.class).create(); + + assertThat(floats) + .isNotEmpty() + .allSatisfy(f -> assertThat(f).isBetween(0f, 100f)); + } + + /** + * We can override global configuration using {@link Settings}. + */ + @Test + void whenCustomSettingsAreProvided_shouldOverrideInstancioProperties() { + // Given + Settings settings = Settings.create() + .set(Keys.FLOAT_MIN, 100f) + .set(Keys.FLOAT_MAX, 200f); + + // When + Set floats = Instancio.ofSet(Float.class) + .withSettings(settings) + .create(); + + // Then + assertThat(floats) + .isNotEmpty() + .allSatisfy(f -> assertThat(f).isBetween(100f, 200f)); + } +} diff --git a/testing-modules/instancio/src/test/java/com/baeldung/instancio/subtype/SubtypeUnitTest.java b/testing-modules/instancio/src/test/java/com/baeldung/instancio/subtype/SubtypeUnitTest.java new file mode 100644 index 0000000000..7bbfaa497f --- /dev/null +++ b/testing-modules/instancio/src/test/java/com/baeldung/instancio/subtype/SubtypeUnitTest.java @@ -0,0 +1,54 @@ +package com.baeldung.instancio.subtype; + +import com.baeldung.instancio.abstracttype.AbstractItem; +import com.baeldung.instancio.generics.Item; +import com.baeldung.instancio.student.model.ContactInfo; +import com.baeldung.instancio.student.model.Student; +import org.instancio.Instancio; +import org.instancio.TypeToken; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.LinkedList; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.instancio.Select.all; +import static org.instancio.Select.field; + +/** + * Using {@code subtype()} method we can specify a specific implementation + * class for an abstract type, or a specialized subclass for a concrete class. + */ +class SubtypeUnitTest { + + @Test + void whenGivenCollectionSubtype_shouldUseSpecifiedCollectionClass() { + // Given + final Class subtype = LinkedList.class; + + // When + Student student = Instancio.of(Student.class) + .subtype(field(ContactInfo::getPhones), subtype) + .create(); + + // Then + assertThat(student.getContactInfo().getPhones()) + .isNotEmpty() + .isExactlyInstanceOf(subtype); + } + + @Test + void whenGivenSubtypeForGenericAbstractType_shouldUseSpecifiedConcreteClass() { + // Given + final Class subtype = Item.class; + + // When + AbstractItem abstractItem = Instancio.of(new TypeToken>() {}) + .subtype(all(AbstractItem.class), subtype) + .create(); + + // Then + assertThat(abstractItem).isExactlyInstanceOf(subtype); + assertThat(abstractItem.getValue()).isNotNull(); + } +} diff --git a/testing-modules/instancio/src/test/resources/instancio.properties b/testing-modules/instancio/src/test/resources/instancio.properties new file mode 100644 index 0000000000..a314af0a3f --- /dev/null +++ b/testing-modules/instancio/src/test/resources/instancio.properties @@ -0,0 +1,2 @@ +float.min=1 +float.max=100 diff --git a/testing-modules/pom.xml b/testing-modules/pom.xml index f237d2d6fc..64546b5064 100644 --- a/testing-modules/pom.xml +++ b/testing-modules/pom.xml @@ -22,6 +22,7 @@ gatling groovy-spock hamcrest + instancio junit-4 junit-5-advanced junit-5-basics