Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@
<!-- This revision id (version) is overridden on deploy, so this revision number is only applicable for local usage -->
<revision>0.10.0</revision>

<spring-boot.version>3.5.6</spring-boot.version>
<spring-boot.version>4.0.2</spring-boot.version>

<code-quality.feature.enabled>true</code-quality.feature.enabled>
<code-quality.configuration.checkstyle.suppression-location>checkstyle/checkstyle-suppressions.xml
</code-quality.configuration.checkstyle.suppression-location>
<code-quality.configuration.checkstyle.suppression-location>checkstyle/checkstyle-suppressions.xml</code-quality.configuration.checkstyle.suppression-location>
</properties>

<modules>
Expand Down
1 change: 0 additions & 1 deletion testify-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
<datafaker.version>2.5.1</datafaker.version>
<java-snapshot-testing-junit5.version>4.0.8</java-snapshot-testing-junit5.version>
<json-path.version>2.9.0</json-path.version>
<spring-boot.version>3.5.4</spring-boot.version>
</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NullMarked
package io.github.finoid.testify.core.internal;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NullMarked
package io.github.finoid.testify.core.type;

import org.jspecify.annotations.NullMarked;
17 changes: 6 additions & 11 deletions testify-snapshot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,18 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<datafaker.version>2.4.3</datafaker.version>
<java-snapshot-testing-junit5.version>4.0.8</java-snapshot-testing-junit5.version>
<json-path.version>2.9.0</json-path.version>
<snapshot-testing.version>0.10.0</snapshot-testing.version>
</properties>

<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
Expand All @@ -57,12 +52,12 @@
</dependency>
<dependency>
<groupId>io.github.finoid</groupId>
<artifactId>snapshot-testing-jackson2</artifactId>
<artifactId>snapshot-testing-jackson3</artifactId>
<version>${snapshot-testing.version}</version>
</dependency>
<dependency>
<groupId>io.github.finoid</groupId>
<artifactId>snapshot-testing-junit5</artifactId>
<artifactId>snapshot-testing-junit6</artifactId>
<version>${snapshot-testing.version}</version>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package io.github.finoid.testify.snapshot;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.extern.slf4j.Slf4j;
import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.module.SimpleModule;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;

/**
Expand All @@ -30,21 +29,24 @@ public DeterministicCollectionModule() {
/**
* Collections get converted into a sorted Object[]. This then gets serialized using the default Array serializer.
*/
@SuppressWarnings("rawtypes")
private static class CollectionSerializer extends JsonSerializer<Collection> {
private static class CollectionSerializer<T> extends ValueSerializer<Collection<T>> {
/**
* Serializes a {@link Collection} into a sorted {@code Object[]} for deterministic output.
*
* @param value the collection to be serialized
* @param gen the JSON generator
* @param serializers the serializer provider
* @throws IOException if an error occurs during serialization
* @param value the collection to be serialized
* @param gen the JSON generator
* @param ctxt the serialization context
*/
@Override
public void serialize(final Collection value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException {
final Object[] sorted = convert(value);
public void serialize(final Collection value, final tools.jackson.core.JsonGenerator gen, final SerializationContext ctxt)
throws JacksonException {
Object[] sorted = convert(value);

serializers.defaultSerializeValue(sorted, gen);
if (value == null) {
ctxt.getDefaultNullValueSerializer().serialize(null, gen, ctxt);
} else {
ctxt.findTypedValueSerializer(Object[].class, true).serialize(sorted, gen, ctxt);
}
}

/**
Expand All @@ -53,21 +55,21 @@ public void serialize(final Collection value, final JsonGenerator gen, final Ser
* @param value the collection to be converted
* @return a sorted array or an unsorted array if sorting fails
*/
private static Object[] convert(@Nullable final Collection<?> value) {
private Object[] convert(final Collection<?> value) {
if (value == null || value.isEmpty()) {
return new Object[0];
return Collections.emptyList().toArray();
}

try {
return value.stream()
.filter(Objects::nonNull)
.sorted()
.toArray();
} catch (final ClassCastException ex) {
log.warn("Sorting failed for collection of type: {}. Example contents: {}.\n"
+ "Consider adding a custom serializer or comparator.", value.getClass().getSimpleName(),
value.stream().limit(3).toList());

} catch (ClassCastException ex) {
log.warn(
"Unable to sort() collection - this may result in a non deterministic snapshot.\n"
+ "Consider adding a custom serializer for this type via the JacksonSnapshotSerializer#configure() method.\n"
+ ex.getMessage());
return value.toArray();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.github.finoid.testify.snapshot;

import io.github.finoid.snapshots.Expect;
import com.fasterxml.jackson.databind.module.SimpleModule;
import io.github.finoid.testify.core.internal.Precondition;
import io.github.finoid.testify.snapshot.SnapshotDsl.MaskingSnapshotDsl;
import tools.jackson.databind.module.SimpleModule;

/**
* A DSL for snapshotting JSON objects with optional masking of fields.
Expand All @@ -29,6 +29,7 @@ public static JsonSnapshotDsl ofExpect(final Expect expect) {
@Override
@SuppressWarnings("NullAway")
public <T> void snapshot(final T toBeSnapshotted) {
// TODO (nw) use the JsonSnapshotSerializer from java-snapshot-testing instead?
expect.serializer(new JsonSnapshotSerializer(maskedFieldPaths, new SimpleModule())) // TODO (nw) option to pass a simple module?
.scenario(scenario)
.toMatchSnapshot(toBeSnapshotted);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
package io.github.finoid.testify.snapshot;

import io.github.finoid.snapshots.Snapshot;
import io.github.finoid.snapshots.SnapshotSerializerContext;
import io.github.finoid.snapshots.exceptions.SnapshotExtensionException;
import io.github.finoid.snapshots.serializers.SerializerType;
import io.github.finoid.snapshots.serializers.SnapshotSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.PrettyPrinter;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.JsonPathException;
import io.github.finoid.snapshots.Snapshot;
import io.github.finoid.snapshots.SnapshotSerializerContext;
import io.github.finoid.snapshots.exceptions.SnapshotExtensionException;
import io.github.finoid.snapshots.serializers.SerializerType;
import io.github.finoid.snapshots.serializers.SnapshotSerializer;
import io.github.finoid.testify.core.internal.Precondition;
import tools.jackson.core.util.DefaultIndenter;
import tools.jackson.core.util.DefaultPrettyPrinter;
import tools.jackson.core.util.Separators;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.cfg.DateTimeFeature;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;

import java.util.Collections;
import java.util.List;
Expand All @@ -39,7 +40,6 @@
public class JsonSnapshotSerializer implements SnapshotSerializer {
private final List<String> maskedFieldPaths;
private final ObjectMapper objectMapper;
private final PrettyPrinter prettyPrinter = new SnapshotPrettyPrinter();

/**
* Creates a new {@code JsonSnapshotSerializer} with a list of masked fields.
Expand Down Expand Up @@ -75,7 +75,7 @@ public Snapshot apply(final Object object, final SnapshotSerializerContext gen)
try {
final List<?> objects = Collections.singletonList(object);

final String body = objectMapper.writer(this.prettyPrinter)
final String body = objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(objects);

final DocumentContext documentContext = JsonPath.parse(body);
Expand Down Expand Up @@ -103,32 +103,27 @@ public String getOutputFormat() {
return SerializerType.JSON.name();
}

@SuppressWarnings("deprecation")
private static ObjectMapper createObjectMapper(final SimpleModule simpleModule) {
final ObjectMapper mapper = new ObjectMapper();

mapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
mapper.enable(SerializationFeature.WRITE_DATES_WITH_ZONE_ID);
mapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);

mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

mapper.findAndRegisterModules();
mapper.registerModule(simpleModule);
mapper.registerModule(new DeterministicCollectionModule());

mapper.setVisibility(
mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
);

return mapper;
final JsonMapper.Builder builder =
JsonMapper.builder()
.defaultPrettyPrinter(new SnapshotPrettyPrinter())
.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
.enable(DateTimeFeature.WRITE_DATES_WITH_ZONE_ID)
.disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.findAndAddModules()
.changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL))
.changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_NULL))
.changeDefaultVisibility(visibility ->
visibility.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));

builder.addModule(simpleModule);
builder.addModule(new DeterministicCollectionModule()); // TODO (nw) use the one from java-snapshot-testing

return builder.build();
}

/**
Expand All @@ -142,13 +137,13 @@ private static ObjectMapper createObjectMapper(final SimpleModule simpleModule)
* </p>
*/
private static class SnapshotPrettyPrinter extends DefaultPrettyPrinter {
public SnapshotPrettyPrinter() {
final Indenter lfOnlyIndenter = new DefaultIndenter(" ", "\n");
this.indentArraysWith(lfOnlyIndenter);
this.indentObjectsWith(lfOnlyIndenter);
private static final DefaultPrettyPrinter.Indenter LF_ONLY_INDENTER = new DefaultIndenter(" ", "\n");
private static final Separators DEFAULT_SEPARATORS = Separators.createDefaultInstance().withRootSeparator("");

this._objectFieldValueSeparatorWithSpaces =
this._separators.getObjectFieldValueSeparator() + " ";
public SnapshotPrettyPrinter() {
this.indentArraysWith(LF_ONLY_INDENTER);
this.indentObjectsWith(LF_ONLY_INDENTER);
this.withSeparators(DEFAULT_SEPARATORS);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public void beforeTestExecution(final ExtensionContext context) {
final ExtensionContext.Store store = context.getRoot()
.getStore(NS);

final State state = store.getOrComputeIfAbsent(KEY_STATE, k -> new State(), State.class);
final State state = store.computeIfAbsent(KEY_STATE, k -> new State(), State.class);

final String commonMethodName = context.getTestMethod()
.map(Method::getName)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NullMarked
package io.github.finoid.testify.snapshot;

import org.jspecify.annotations.NullMarked;
Loading