From 6c3fd565748af5b7a6c47314b69e866598c68a00 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Wed, 11 Sep 2024 13:52:30 +0200 Subject: [PATCH 1/5] update toolbox --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dd9bd70..f6b039f 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ it.aboutbits spring-boot-toolbox - 1.0.0-RC1 + 1.0.0-RC2 From 85fd9b7110ddeb16c93846cf8d1dab27375f2db2 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Wed, 11 Sep 2024 13:52:46 +0200 Subject: [PATCH 2/5] add some suppress-warnings --- .../testing/validation/core/BaseValidationAssert.java | 5 ++++- .../testing/validation/source/BiggerThanValueSource.java | 1 + .../testing/validation/source/BlankValueSource.java | 1 + .../testing/validation/source/EmptyValueSource.java | 1 + .../testing/validation/source/FutureValueSource.java | 1 + .../testing/validation/source/LessThanValueSource.java | 1 + .../testing/validation/source/NullValueSource.java | 1 + .../testing/validation/source/PastValueSource.java | 1 + .../testing/validation/source/ZeroValueSource.java | 1 + 9 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java index 6edee7b..af80537 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java @@ -34,6 +34,7 @@ public abstract class BaseValidationAssert> { * * @param type The class to whitelist. */ + @SuppressWarnings("unused") public static void registerNonBeanType(Class type) { NON_BEAN_TYPES.add(type); } @@ -48,16 +49,18 @@ public

CallBuilder of(@NonNull P parameterUnderTest) { public static final class CallBuilder, P> { private final BaseValidationAssert parent; + @SuppressWarnings("unused") public R calling(@NonNull Consumer

functionToCallWithParameter) { parent.setFunctionToCallWithParameter(functionToCallWithParameter); return parent.ruleBuilder; } + @SuppressWarnings("unused") public R usingBeanValidation() { return parent.ruleBuilder; } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unused", "unchecked"}) public R calling( @NonNull BiConsumer functionToCallWithParameter, @NonNull ID id diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java index 4343980..2072ed2 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java @@ -32,6 +32,7 @@ public class BiggerThanValueSource implements ValueSource { TYPE_SOURCES.put(ScaledBigDecimal.class, BiggerThanValueSource::getScaledBigDecimalStream); } + @SuppressWarnings("unchecked") public static void registerType(Class type, Function> source) { TYPE_SOURCES.put(type, source); } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java index 035d6d4..c478ebd 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java @@ -17,6 +17,7 @@ public class BlankValueSource implements ValueSource { ); } + @SuppressWarnings("unused") public static void registerType(Class type, Function> source) { TYPE_SOURCES.put(type, source); } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java index 318c13d..cb8539a 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java @@ -29,6 +29,7 @@ public class EmptyValueSource implements ValueSource { ); } + @SuppressWarnings("unused") public static void registerType(Class type, Function> source) { TYPE_SOURCES.put(type, source); } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java index 2441a06..59d73f6 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java @@ -20,6 +20,7 @@ public class FutureValueSource implements ValueSource { TYPE_SOURCES.put(OffsetDateTime.class, FutureValueSource::getOffsetDateTimeStream); } + @SuppressWarnings("unused") public static void registerType(Class type, Function> source) { TYPE_SOURCES.put(type, source); } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java index df02ab9..17bae11 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java @@ -33,6 +33,7 @@ public class LessThanValueSource implements ValueSource { TYPE_SOURCES.put(ScaledBigDecimal.class, LessThanValueSource::getScaledBigDecimalStream); } + @SuppressWarnings("unused") public static void registerType(Class type, Function> source) { TYPE_SOURCES.put(type, source); } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java index 0656bdf..3bb9c5c 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java @@ -10,6 +10,7 @@ public class NullValueSource implements ValueSource { private static final Map, Function>> TYPE_SOURCES = new HashMap<>(); + @SuppressWarnings("unused") public static void registerType(Class type, Function> source) { TYPE_SOURCES.put(type, source); } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java index f8b2271..d50edd8 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java @@ -20,6 +20,7 @@ public class PastValueSource implements ValueSource { TYPE_SOURCES.put(OffsetDateTime.class, PastValueSource::getOffsetDateTimeStream); } + @SuppressWarnings("unused") public static void registerType(Class type, Function> source) { TYPE_SOURCES.put(type, source); } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java index b0a7c02..ecaa312 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java @@ -29,6 +29,7 @@ public class ZeroValueSource implements ValueSource { TYPE_SOURCES.put(ScaledBigDecimal.class, (Object[] args) -> Stream.of(ScaledBigDecimal.valueOf(0))); } + @SuppressWarnings("unused") public static void registerType(Class type, Function> source) { TYPE_SOURCES.put(type, source); } From 844b4dc4e88f18567c07b717b2a17a41323bd43b Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Wed, 11 Sep 2024 13:53:01 +0200 Subject: [PATCH 3/5] add BeanAccessor --- .../testing/spring/BeanAccessor.java | 23 +++++++++++++++++++ .../testing/spring/EnableBeanAccessor.java | 14 +++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/main/java/it/aboutbits/springboot/testing/spring/BeanAccessor.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/spring/EnableBeanAccessor.java diff --git a/src/main/java/it/aboutbits/springboot/testing/spring/BeanAccessor.java b/src/main/java/it/aboutbits/springboot/testing/spring/BeanAccessor.java new file mode 100644 index 0000000..267e8d9 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/spring/BeanAccessor.java @@ -0,0 +1,23 @@ +package it.aboutbits.springboot.testing.spring; + +import lombok.NonNull; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +public class BeanAccessor implements ApplicationContextAware { + private static ApplicationContext applicationContext; + + public static T getBean(@NonNull Class clazz) { + if (applicationContext == null) { + throw new IllegalStateException("ApplicationContext is not set. Did you @EnableBeanAccessor?"); + } + return applicationContext.getBean(clazz); + } + + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) { + BeanAccessor.applicationContext = applicationContext; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/spring/EnableBeanAccessor.java b/src/main/java/it/aboutbits/springboot/testing/spring/EnableBeanAccessor.java new file mode 100644 index 0000000..3cf0664 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/spring/EnableBeanAccessor.java @@ -0,0 +1,14 @@ +package it.aboutbits.springboot.testing.spring; + +import org.springframework.context.annotation.Import; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Import(BeanAccessor.class) +public @interface EnableBeanAccessor { +} From a2c82654e48ea024134a5e131ed82d98e29443df Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Wed, 11 Sep 2024 13:53:18 +0200 Subject: [PATCH 4/5] add PersistenceAssert --- .../persistance/PersistenceAssert.java | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 src/main/java/it/aboutbits/springboot/testing/persistance/PersistenceAssert.java diff --git a/src/main/java/it/aboutbits/springboot/testing/persistance/PersistenceAssert.java b/src/main/java/it/aboutbits/springboot/testing/persistance/PersistenceAssert.java new file mode 100644 index 0000000..c08a6c6 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/persistance/PersistenceAssert.java @@ -0,0 +1,146 @@ +package it.aboutbits.springboot.testing.persistance; + +import it.aboutbits.springboot.testing.spring.BeanAccessor; +import it.aboutbits.springboot.toolbox.persistence.ChangeAware; +import it.aboutbits.springboot.toolbox.type.identity.EntityId; +import it.aboutbits.springboot.toolbox.type.identity.Identified; +import jakarta.persistence.EntityManager; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +import static org.assertj.core.api.Assertions.assertThat; + +public final class PersistenceAssert { + private PersistenceAssert() { + } + + private static EntityManager getEntityManager() { + return BeanAccessor.getBean(EntityManager.class); + } + + @SuppressWarnings("unused") + public static & ChangeAware> WriteOperationAsserter assertThatEntity(@NonNull E before) { + return new WriteOperationAsserter<>(getEntityManager(), before); + } + + @SuppressWarnings("unused") + public static > WriteOperationIdAsserter assertThatEntity(@NonNull ID id) { + return new WriteOperationIdAsserter<>(getEntityManager(), id); + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + public static final class WriteOperationAsserter & ChangeAware> { + private final EntityManager entityManager; + private final E entity; + + @SuppressWarnings("unused") + public void hasBeenCreatedInDatabase() { + entityManager.clear(); + var savedInstance = getSavedInstance(); + + assertThat( + savedInstance.getId() + ).isNotNull(); + + assertThat( + savedInstance.getCreatedAt() + ).isNotNull(); + } + + @SuppressWarnings("unused") + public void hasBeenUpdatedInDatabase() { + entityManager.clear(); + var savedInstance = getSavedInstance(); + + assertThat( + savedInstance.getUpdatedAt() + ).isAfter( + entity.getUpdatedAt() + ); + } + + @SuppressWarnings("unused") + public void hasNotChangedInDatabase() { + entityManager.clear(); + var savedInstance = getSavedInstance(); + + assertThat(savedInstance).isNotNull(); + + assertThat( + savedInstance.getUpdatedAt() + ).isEqualTo( + entity.getUpdatedAt() + ); + } + + @SuppressWarnings("unused") + public void isAbsentInDatabase() { + entityManager.clear(); + var savedInstance = getSavedInstance(); + + assertThat(savedInstance).isNull(); + } + + @SuppressWarnings("unused") + public void isPresentInDatabase() { + entityManager.clear(); + var savedInstance = getSavedInstance(); + + assertThat(savedInstance).isNotNull(); + } + + @SuppressWarnings("unchecked") + private E getSavedInstance() { + return (E) entityManager.find( + entity.getClass(), + entity.getId() + ); + } + } + + public static final class WriteOperationIdAsserter> { + private final EntityManager entityManager; + private final ID id; + private final Class> clazz; + + @SuppressWarnings("unchecked") + private WriteOperationIdAsserter( + EntityManager entityManager, + ID id + ) { + this.entityManager = entityManager; + this.id = id; + + var enclosingClass = id.getClass().getEnclosingClass(); + if (Identified.class.isAssignableFrom(enclosingClass)) { + this.clazz = (Class>) enclosingClass; + } else { + throw new IllegalArgumentException("EntityId is not a subclass of Identified!"); + } + } + + @SuppressWarnings("unused") + public void isAbsentInDatabase() { + entityManager.clear(); + var savedInstance = getSavedInstance(); + + assertThat(savedInstance).isNull(); + } + + @SuppressWarnings("unused") + public void isPresentInDatabase() { + entityManager.clear(); + var savedInstance = getSavedInstance(); + + assertThat(savedInstance).isNotNull(); + } + + private Object getSavedInstance() { + return entityManager.find( + clazz, + id + ); + } + } +} From 273b22a5ba7ccc71dd7ea4d3abf35fd028721624 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Wed, 11 Sep 2024 13:53:30 +0200 Subject: [PATCH 5/5] add TestData base classes --- .../base/ModifyableTestDataCreator.java | 95 +++++++++++++++++++ .../testdata/base/TestDataCreator.java | 57 +++++++++++ 2 files changed, 152 insertions(+) create mode 100644 src/main/java/it/aboutbits/springboot/testing/testdata/base/ModifyableTestDataCreator.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/testdata/base/TestDataCreator.java diff --git a/src/main/java/it/aboutbits/springboot/testing/testdata/base/ModifyableTestDataCreator.java b/src/main/java/it/aboutbits/springboot/testing/testdata/base/ModifyableTestDataCreator.java new file mode 100644 index 0000000..bd12e1c --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/testdata/base/ModifyableTestDataCreator.java @@ -0,0 +1,95 @@ +package it.aboutbits.springboot.testing.testdata.base; + +import it.aboutbits.springboot.testing.spring.BeanAccessor; +import jakarta.persistence.EntityManager; +import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.support.TransactionTemplate; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.ObjIntConsumer; +import java.util.function.UnaryOperator; + +@SuppressWarnings("unchecked") +@Slf4j +public abstract class ModifyableTestDataCreator, ITEM, PARAMETER> extends TestDataCreator { + private boolean mutatorSet = false; + private boolean mutatorCalled = false; + + protected BiFunction parameterMutator = (parameter, index) -> { + mutatorCalled = true; + return parameter; + }; + protected ObjIntConsumer resultMutator = (item, index) -> { + }; + + protected ModifyableTestDataCreator(int count) { + super(count); + } + + public CREATOR modifyParameter(BiFunction parameterMutator) { + this.parameterMutator = (parameter, index) -> { + mutatorCalled = true; + return parameterMutator.apply(parameter, index); + }; + mutatorSet = true; + return (CREATOR) this; + } + + public CREATOR modifyParameter(UnaryOperator parameterMutator) { + this.parameterMutator = (parameter, index) -> { + mutatorCalled = true; + return parameterMutator.apply(parameter); + }; + mutatorSet = true; + return (CREATOR) this; + } + + public CREATOR modifyResult(ObjIntConsumer resultMutator) { + this.resultMutator = resultMutator; + return (CREATOR) this; + } + + public CREATOR modifyResult(Consumer resultMutator) { + this.resultMutator = (item, index) -> resultMutator.accept(item); + return (CREATOR) this; + } + + @Override + protected List create() { + var result = new ArrayList(); + + for (var index = 0; index < numberOfItems; index++) { + var item = create(index); + + resultMutator.accept(item, index); + + result.add( + saveMutation(item) + ); + } + + if (mutatorSet && !mutatorCalled) { + log.error("Parameter-mutation is defined but was never called."); + throw new IllegalStateException("Parameter-mutation is defined but was never called."); + } + + return result; + } + + protected ITEM saveMutation(ITEM item) { + var entityManager = BeanAccessor.getBean(EntityManager.class); + var transactionTemplate = BeanAccessor.getBean(TransactionTemplate.class); + + return transactionTemplate.execute(status -> { + try { + return entityManager.merge(item); + } catch (Exception e) { + status.setRollbackOnly(); + throw e; + } + }); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/testdata/base/TestDataCreator.java b/src/main/java/it/aboutbits/springboot/testing/testdata/base/TestDataCreator.java new file mode 100644 index 0000000..3a23885 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/testdata/base/TestDataCreator.java @@ -0,0 +1,57 @@ +package it.aboutbits.springboot.testing.testdata.base; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +public abstract class TestDataCreator { + protected final int numberOfItems; + + protected TestDataCreator(int numberIfItems) { + this.numberOfItems = numberIfItems; + } + + public void commit() { + create(); + } + + public ITEM returnFirst() { + return create().getFirst(); + } + + public List returnAll() { + return create(); + } + + public List returnSorted(Comparator comparator) { + return returnAll().stream().sorted(comparator).toList(); + } + + public > List returnSorted(Function comparator) { + return returnAll().stream() + .sorted(Comparator.comparing(comparator)) + .toList(); + } + + public Set returnSet() { + return new HashSet<>(create()); + } + + protected List create() { + var result = new ArrayList(); + + for (var index = 0; index < numberOfItems; index++) { + result.add( + create(index) + ); + } + + return result; + } + + protected abstract ITEM create(int index); + +}