Skip to content

Commit 53a371a

Browse files
authored
Merge pull request #3 from aboutbits/ab-251-add-test-tooling-from-aichner-in-boilerplate
Ab 251 add test tooling from aichner in boilerplate
2 parents c90e85e + 273b22a commit 53a371a

15 files changed

Lines changed: 348 additions & 2 deletions

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<dependency>
2424
<groupId>it.aboutbits</groupId>
2525
<artifactId>spring-boot-toolbox</artifactId>
26-
<version>1.0.0-RC1</version>
26+
<version>1.0.0-RC2</version>
2727
</dependency>
2828

2929
<dependency>
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package it.aboutbits.springboot.testing.persistance;
2+
3+
import it.aboutbits.springboot.testing.spring.BeanAccessor;
4+
import it.aboutbits.springboot.toolbox.persistence.ChangeAware;
5+
import it.aboutbits.springboot.toolbox.type.identity.EntityId;
6+
import it.aboutbits.springboot.toolbox.type.identity.Identified;
7+
import jakarta.persistence.EntityManager;
8+
import lombok.AccessLevel;
9+
import lombok.NonNull;
10+
import lombok.RequiredArgsConstructor;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
14+
public final class PersistenceAssert {
15+
private PersistenceAssert() {
16+
}
17+
18+
private static EntityManager getEntityManager() {
19+
return BeanAccessor.getBean(EntityManager.class);
20+
}
21+
22+
@SuppressWarnings("unused")
23+
public static <E extends Identified<?> & ChangeAware> WriteOperationAsserter<E> assertThatEntity(@NonNull E before) {
24+
return new WriteOperationAsserter<>(getEntityManager(), before);
25+
}
26+
27+
@SuppressWarnings("unused")
28+
public static <ID extends EntityId<?>> WriteOperationIdAsserter<ID> assertThatEntity(@NonNull ID id) {
29+
return new WriteOperationIdAsserter<>(getEntityManager(), id);
30+
}
31+
32+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
33+
public static final class WriteOperationAsserter<E extends Identified<?> & ChangeAware> {
34+
private final EntityManager entityManager;
35+
private final E entity;
36+
37+
@SuppressWarnings("unused")
38+
public void hasBeenCreatedInDatabase() {
39+
entityManager.clear();
40+
var savedInstance = getSavedInstance();
41+
42+
assertThat(
43+
savedInstance.getId()
44+
).isNotNull();
45+
46+
assertThat(
47+
savedInstance.getCreatedAt()
48+
).isNotNull();
49+
}
50+
51+
@SuppressWarnings("unused")
52+
public void hasBeenUpdatedInDatabase() {
53+
entityManager.clear();
54+
var savedInstance = getSavedInstance();
55+
56+
assertThat(
57+
savedInstance.getUpdatedAt()
58+
).isAfter(
59+
entity.getUpdatedAt()
60+
);
61+
}
62+
63+
@SuppressWarnings("unused")
64+
public void hasNotChangedInDatabase() {
65+
entityManager.clear();
66+
var savedInstance = getSavedInstance();
67+
68+
assertThat(savedInstance).isNotNull();
69+
70+
assertThat(
71+
savedInstance.getUpdatedAt()
72+
).isEqualTo(
73+
entity.getUpdatedAt()
74+
);
75+
}
76+
77+
@SuppressWarnings("unused")
78+
public void isAbsentInDatabase() {
79+
entityManager.clear();
80+
var savedInstance = getSavedInstance();
81+
82+
assertThat(savedInstance).isNull();
83+
}
84+
85+
@SuppressWarnings("unused")
86+
public void isPresentInDatabase() {
87+
entityManager.clear();
88+
var savedInstance = getSavedInstance();
89+
90+
assertThat(savedInstance).isNotNull();
91+
}
92+
93+
@SuppressWarnings("unchecked")
94+
private E getSavedInstance() {
95+
return (E) entityManager.find(
96+
entity.getClass(),
97+
entity.getId()
98+
);
99+
}
100+
}
101+
102+
public static final class WriteOperationIdAsserter<ID extends EntityId<?>> {
103+
private final EntityManager entityManager;
104+
private final ID id;
105+
private final Class<? extends Identified<? extends ID>> clazz;
106+
107+
@SuppressWarnings("unchecked")
108+
private WriteOperationIdAsserter(
109+
EntityManager entityManager,
110+
ID id
111+
) {
112+
this.entityManager = entityManager;
113+
this.id = id;
114+
115+
var enclosingClass = id.getClass().getEnclosingClass();
116+
if (Identified.class.isAssignableFrom(enclosingClass)) {
117+
this.clazz = (Class<? extends Identified<? extends ID>>) enclosingClass;
118+
} else {
119+
throw new IllegalArgumentException("EntityId is not a subclass of Identified!");
120+
}
121+
}
122+
123+
@SuppressWarnings("unused")
124+
public void isAbsentInDatabase() {
125+
entityManager.clear();
126+
var savedInstance = getSavedInstance();
127+
128+
assertThat(savedInstance).isNull();
129+
}
130+
131+
@SuppressWarnings("unused")
132+
public void isPresentInDatabase() {
133+
entityManager.clear();
134+
var savedInstance = getSavedInstance();
135+
136+
assertThat(savedInstance).isNotNull();
137+
}
138+
139+
private Object getSavedInstance() {
140+
return entityManager.find(
141+
clazz,
142+
id
143+
);
144+
}
145+
}
146+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package it.aboutbits.springboot.testing.spring;
2+
3+
import lombok.NonNull;
4+
import org.springframework.context.ApplicationContext;
5+
import org.springframework.context.ApplicationContextAware;
6+
import org.springframework.stereotype.Component;
7+
8+
@Component
9+
public class BeanAccessor implements ApplicationContextAware {
10+
private static ApplicationContext applicationContext;
11+
12+
public static <T> T getBean(@NonNull Class<T> clazz) {
13+
if (applicationContext == null) {
14+
throw new IllegalStateException("ApplicationContext is not set. Did you @EnableBeanAccessor?");
15+
}
16+
return applicationContext.getBean(clazz);
17+
}
18+
19+
@Override
20+
public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
21+
BeanAccessor.applicationContext = applicationContext;
22+
}
23+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package it.aboutbits.springboot.testing.spring;
2+
3+
import org.springframework.context.annotation.Import;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
@Target({ElementType.TYPE})
11+
@Retention(RetentionPolicy.RUNTIME)
12+
@Import(BeanAccessor.class)
13+
public @interface EnableBeanAccessor {
14+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package it.aboutbits.springboot.testing.testdata.base;
2+
3+
import it.aboutbits.springboot.testing.spring.BeanAccessor;
4+
import jakarta.persistence.EntityManager;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springframework.transaction.support.TransactionTemplate;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.function.BiFunction;
11+
import java.util.function.Consumer;
12+
import java.util.function.ObjIntConsumer;
13+
import java.util.function.UnaryOperator;
14+
15+
@SuppressWarnings("unchecked")
16+
@Slf4j
17+
public abstract class ModifyableTestDataCreator<CREATOR extends ModifyableTestDataCreator<CREATOR, ITEM, PARAMETER>, ITEM, PARAMETER> extends TestDataCreator<ITEM> {
18+
private boolean mutatorSet = false;
19+
private boolean mutatorCalled = false;
20+
21+
protected BiFunction<PARAMETER, Integer, PARAMETER> parameterMutator = (parameter, index) -> {
22+
mutatorCalled = true;
23+
return parameter;
24+
};
25+
protected ObjIntConsumer<ITEM> resultMutator = (item, index) -> {
26+
};
27+
28+
protected ModifyableTestDataCreator(int count) {
29+
super(count);
30+
}
31+
32+
public CREATOR modifyParameter(BiFunction<PARAMETER, Integer, PARAMETER> parameterMutator) {
33+
this.parameterMutator = (parameter, index) -> {
34+
mutatorCalled = true;
35+
return parameterMutator.apply(parameter, index);
36+
};
37+
mutatorSet = true;
38+
return (CREATOR) this;
39+
}
40+
41+
public CREATOR modifyParameter(UnaryOperator<PARAMETER> parameterMutator) {
42+
this.parameterMutator = (parameter, index) -> {
43+
mutatorCalled = true;
44+
return parameterMutator.apply(parameter);
45+
};
46+
mutatorSet = true;
47+
return (CREATOR) this;
48+
}
49+
50+
public CREATOR modifyResult(ObjIntConsumer<ITEM> resultMutator) {
51+
this.resultMutator = resultMutator;
52+
return (CREATOR) this;
53+
}
54+
55+
public CREATOR modifyResult(Consumer<ITEM> resultMutator) {
56+
this.resultMutator = (item, index) -> resultMutator.accept(item);
57+
return (CREATOR) this;
58+
}
59+
60+
@Override
61+
protected List<ITEM> create() {
62+
var result = new ArrayList<ITEM>();
63+
64+
for (var index = 0; index < numberOfItems; index++) {
65+
var item = create(index);
66+
67+
resultMutator.accept(item, index);
68+
69+
result.add(
70+
saveMutation(item)
71+
);
72+
}
73+
74+
if (mutatorSet && !mutatorCalled) {
75+
log.error("Parameter-mutation is defined but was never called.");
76+
throw new IllegalStateException("Parameter-mutation is defined but was never called.");
77+
}
78+
79+
return result;
80+
}
81+
82+
protected ITEM saveMutation(ITEM item) {
83+
var entityManager = BeanAccessor.getBean(EntityManager.class);
84+
var transactionTemplate = BeanAccessor.getBean(TransactionTemplate.class);
85+
86+
return transactionTemplate.execute(status -> {
87+
try {
88+
return entityManager.merge(item);
89+
} catch (Exception e) {
90+
status.setRollbackOnly();
91+
throw e;
92+
}
93+
});
94+
}
95+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package it.aboutbits.springboot.testing.testdata.base;
2+
3+
import java.util.ArrayList;
4+
import java.util.Comparator;
5+
import java.util.HashSet;
6+
import java.util.List;
7+
import java.util.Set;
8+
import java.util.function.Function;
9+
10+
public abstract class TestDataCreator<ITEM> {
11+
protected final int numberOfItems;
12+
13+
protected TestDataCreator(int numberIfItems) {
14+
this.numberOfItems = numberIfItems;
15+
}
16+
17+
public void commit() {
18+
create();
19+
}
20+
21+
public ITEM returnFirst() {
22+
return create().getFirst();
23+
}
24+
25+
public List<ITEM> returnAll() {
26+
return create();
27+
}
28+
29+
public List<ITEM> returnSorted(Comparator<ITEM> comparator) {
30+
return returnAll().stream().sorted(comparator).toList();
31+
}
32+
33+
public <U extends Comparable<? super U>> List<ITEM> returnSorted(Function<ITEM, U> comparator) {
34+
return returnAll().stream()
35+
.sorted(Comparator.comparing(comparator))
36+
.toList();
37+
}
38+
39+
public Set<ITEM> returnSet() {
40+
return new HashSet<>(create());
41+
}
42+
43+
protected List<ITEM> create() {
44+
var result = new ArrayList<ITEM>();
45+
46+
for (var index = 0; index < numberOfItems; index++) {
47+
result.add(
48+
create(index)
49+
);
50+
}
51+
52+
return result;
53+
}
54+
55+
protected abstract ITEM create(int index);
56+
57+
}

src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public abstract class BaseValidationAssert<R extends BaseRuleBuilder<?>> {
3434
*
3535
* @param type The class to whitelist.
3636
*/
37+
@SuppressWarnings("unused")
3738
public static void registerNonBeanType(Class<?> type) {
3839
NON_BEAN_TYPES.add(type);
3940
}
@@ -48,16 +49,18 @@ public <P> CallBuilder<R, P> of(@NonNull P parameterUnderTest) {
4849
public static final class CallBuilder<R extends BaseRuleBuilder<?>, P> {
4950
private final BaseValidationAssert<R> parent;
5051

52+
@SuppressWarnings("unused")
5153
public R calling(@NonNull Consumer<P> functionToCallWithParameter) {
5254
parent.setFunctionToCallWithParameter(functionToCallWithParameter);
5355
return parent.ruleBuilder;
5456
}
5557

58+
@SuppressWarnings("unused")
5659
public R usingBeanValidation() {
5760
return parent.ruleBuilder;
5861
}
5962

60-
@SuppressWarnings("unchecked")
63+
@SuppressWarnings({"unused", "unchecked"})
6164
public <ID> R calling(
6265
@NonNull BiConsumer<ID, P> functionToCallWithParameter,
6366
@NonNull ID id

src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public class BiggerThanValueSource implements ValueSource {
3232
TYPE_SOURCES.put(ScaledBigDecimal.class, BiggerThanValueSource::getScaledBigDecimalStream);
3333
}
3434

35+
@SuppressWarnings("unchecked")
3536
public static void registerType(Class<?> type, Function<Object[], Stream<?>> source) {
3637
TYPE_SOURCES.put(type, source);
3738
}

src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class BlankValueSource implements ValueSource {
1717
);
1818
}
1919

20+
@SuppressWarnings("unused")
2021
public static void registerType(Class<?> type, Function<Object[], Stream<?>> source) {
2122
TYPE_SOURCES.put(type, source);
2223
}

0 commit comments

Comments
 (0)