diff --git a/pom.xml b/pom.xml index 5e35cc4..06ba5af 100644 --- a/pom.xml +++ b/pom.xml @@ -35,11 +35,10 @@ 0.10.0 - 3.5.6 + 4.0.2 true - checkstyle/checkstyle-suppressions.xml - + checkstyle/checkstyle-suppressions.xml diff --git a/testify-core/pom.xml b/testify-core/pom.xml index ae30589..3c546a1 100644 --- a/testify-core/pom.xml +++ b/testify-core/pom.xml @@ -24,7 +24,6 @@ 2.5.1 4.0.8 2.9.0 - 3.5.4 diff --git a/testify-core/src/main/java/io/github/finoid/testify/core/internal/package-info.java b/testify-core/src/main/java/io/github/finoid/testify/core/internal/package-info.java new file mode 100644 index 0000000..a395fc2 --- /dev/null +++ b/testify-core/src/main/java/io/github/finoid/testify/core/internal/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package io.github.finoid.testify.core.internal; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/testify-core/src/main/java/io/github/finoid/testify/core/type/package-info.java b/testify-core/src/main/java/io/github/finoid/testify/core/type/package-info.java new file mode 100644 index 0000000..82699cd --- /dev/null +++ b/testify-core/src/main/java/io/github/finoid/testify/core/type/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package io.github.finoid.testify.core.type; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/testify-snapshot/pom.xml b/testify-snapshot/pom.xml index 832ac3f..e3774f0 100644 --- a/testify-snapshot/pom.xml +++ b/testify-snapshot/pom.xml @@ -22,23 +22,18 @@ UTF-8 2.4.3 - 4.0.8 2.9.0 0.10.0 - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 + tools.jackson.core + jackson-core - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 + tools.jackson.core + jackson-databind com.jayway.jsonpath @@ -57,12 +52,12 @@ io.github.finoid - snapshot-testing-jackson2 + snapshot-testing-jackson3 ${snapshot-testing.version} io.github.finoid - snapshot-testing-junit5 + snapshot-testing-junit6 ${snapshot-testing.version} diff --git a/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/DeterministicCollectionModule.java b/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/DeterministicCollectionModule.java index 7e72965..093a852 100644 --- a/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/DeterministicCollectionModule.java +++ b/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/DeterministicCollectionModule.java @@ -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; /** @@ -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 { + private static class CollectionSerializer extends ValueSerializer> { /** * 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); + } } /** @@ -53,9 +55,9 @@ 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 { @@ -63,11 +65,11 @@ private static Object[] convert(@Nullable final Collection value) { .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(); } } diff --git a/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/JsonSnapshotDsl.java b/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/JsonSnapshotDsl.java index 21afffd..5d290f4 100644 --- a/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/JsonSnapshotDsl.java +++ b/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/JsonSnapshotDsl.java @@ -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. @@ -29,6 +29,7 @@ public static JsonSnapshotDsl ofExpect(final Expect expect) { @Override @SuppressWarnings("NullAway") public 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); diff --git a/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/JsonSnapshotSerializer.java b/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/JsonSnapshotSerializer.java index 30da159..9996983 100644 --- a/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/JsonSnapshotSerializer.java +++ b/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/JsonSnapshotSerializer.java @@ -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; @@ -39,7 +40,6 @@ public class JsonSnapshotSerializer implements SnapshotSerializer { private final List maskedFieldPaths; private final ObjectMapper objectMapper; - private final PrettyPrinter prettyPrinter = new SnapshotPrettyPrinter(); /** * Creates a new {@code JsonSnapshotSerializer} with a list of masked fields. @@ -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); @@ -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(); } /** @@ -142,13 +137,13 @@ private static ObjectMapper createObjectMapper(final SimpleModule simpleModule) *

*/ 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 diff --git a/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/SnapshotterExtension.java b/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/SnapshotterExtension.java index e274d37..fcbc63f 100644 --- a/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/SnapshotterExtension.java +++ b/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/SnapshotterExtension.java @@ -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) diff --git a/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/package-info.java b/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/package-info.java new file mode 100644 index 0000000..0c82970 --- /dev/null +++ b/testify-snapshot/src/main/java/io/github/finoid/testify/snapshot/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package io.github.finoid.testify.snapshot; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/testify-spring-autoconfigure/pom.xml b/testify-spring-autoconfigure/pom.xml index bf9ee50..171677b 100644 --- a/testify-spring-autoconfigure/pom.xml +++ b/testify-spring-autoconfigure/pom.xml @@ -48,4 +48,86 @@ spring-boot-configuration-processor
+ + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + org.codehaus.mojo + extra-enforcer-rules + ${extra-enforcer-rules.version} + + + + + enforce-versions + + enforce + + + + + + true + + module-info + + org/jspecify/annotations/NullMarked + org/jspecify/annotations/NullUnmarked + org/jspecify/annotations/Nullable + org/jspecify/annotations/NonNull + + true + + + + + WARN + + org.apache.maven.plugins:maven-verifier-plugin + + Please consider using the maven-invoker-plugin + (http://maven.apache.org/plugins/maven-invoker-plugin/)! + + + + + log4j:log4j + + log4j:log4j:[,2.15.0) + + + + + + JAVA_HOME + + + [17,) + Java 17 or higher is required + + + ${maven.minimum.version} + + + project.version + java.version + You must set the ${property} property! + + + + No Snapshots allowed + true + + + + + + + + \ No newline at end of file diff --git a/testify-spring-autoconfigure/src/main/java/io/github/finoid/testify/spring/autoconfigure/TestifyAutoConfiguration.java b/testify-spring-autoconfigure/src/main/java/io/github/finoid/testify/spring/autoconfigure/TestifyAutoConfiguration.java index 6548acc..3727da9 100644 --- a/testify-spring-autoconfigure/src/main/java/io/github/finoid/testify/spring/autoconfigure/TestifyAutoConfiguration.java +++ b/testify-spring-autoconfigure/src/main/java/io/github/finoid/testify/spring/autoconfigure/TestifyAutoConfiguration.java @@ -1,12 +1,12 @@ package io.github.finoid.testify.spring.autoconfigure; -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.finoid.testify.spring.http.HttpAsserter; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.test.web.servlet.MockMvc; +import tools.jackson.databind.ObjectMapper; @AutoConfiguration public class TestifyAutoConfiguration { diff --git a/testify-spring-autoconfigure/src/test/java/io/github/finoid/testify/spring/autoconfigure/TestifyAutoConfigurationTest.java b/testify-spring-autoconfigure/src/test/java/io/github/finoid/testify/spring/autoconfigure/TestifyAutoConfigurationTest.java index 8152084..ee05384 100644 --- a/testify-spring-autoconfigure/src/test/java/io/github/finoid/testify/spring/autoconfigure/TestifyAutoConfigurationTest.java +++ b/testify-spring-autoconfigure/src/test/java/io/github/finoid/testify/spring/autoconfigure/TestifyAutoConfigurationTest.java @@ -1,6 +1,5 @@ package io.github.finoid.testify.spring.autoconfigure; -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.finoid.testify.spring.http.HttpAsserter; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -9,6 +8,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.test.web.servlet.MockMvc; +import tools.jackson.databind.ObjectMapper; import static org.assertj.core.api.Assertions.assertThat; diff --git a/testify-spring/pom.xml b/testify-spring/pom.xml index 2c0c46a..d6f86fb 100644 --- a/testify-spring/pom.xml +++ b/testify-spring/pom.xml @@ -39,16 +39,106 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter-webmvc + + + org.springframework.boot + spring-boot-starter-webmvc-test org.springframework.boot spring-boot-starter-test + + org.springframework.boot + spring-boot-starter-restclient-test + org.wiremock wiremock-standalone ${wiremock-standalone.version}
+ + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + org.codehaus.mojo + extra-enforcer-rules + ${extra-enforcer-rules.version} + + + + + enforce-versions + + enforce + + + + + + true + + module-info + + org/jspecify/annotations/NullMarked + org/jspecify/annotations/NullUnmarked + org/jspecify/annotations/Nullable + org/jspecify/annotations/NonNull + + true + + + + + WARN + + org.apache.maven.plugins:maven-verifier-plugin + + Please consider using the maven-invoker-plugin + (http://maven.apache.org/plugins/maven-invoker-plugin/)! + + + + + log4j:log4j + + log4j:log4j:[,2.15.0) + + + + + + JAVA_HOME + + + [17,) + Java 17 or higher is required + + + ${maven.minimum.version} + + + project.version + java.version + You must set the ${property} property! + + + + No Snapshots allowed + true + + + + + + + + \ No newline at end of file diff --git a/testify-spring/src/main/java/io/github/finoid/testify/spring/http/HttpAsserter.java b/testify-spring/src/main/java/io/github/finoid/testify/spring/http/HttpAsserter.java index e1da06a..2b1d232 100644 --- a/testify-spring/src/main/java/io/github/finoid/testify/spring/http/HttpAsserter.java +++ b/testify-spring/src/main/java/io/github/finoid/testify/spring/http/HttpAsserter.java @@ -1,8 +1,5 @@ package io.github.finoid.testify.spring.http; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.finoid.testify.core.internal.Precondition; import io.github.finoid.testify.spring.http.HttpAsserter.ExpectSpec.ExpectDsl; import io.github.finoid.testify.spring.http.servlet.MockMvcBuilder; @@ -11,7 +8,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; import lombok.Value; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Assertions; @@ -28,6 +24,8 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.validation.Validator; import org.springframework.web.servlet.HandlerExceptionResolver; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; import java.util.Collection; import java.util.Collections; @@ -135,7 +133,6 @@ private ResultActions request(final RequestSpec requestSpec) throws Exception { ); } - @SneakyThrows(JsonProcessingException.class) private String writeValueAsString(@org.jspecify.annotations.Nullable final I request) { if (request == null) { return ""; @@ -532,7 +529,7 @@ public static class RequestSpecDsl { String url; HttpMethod httpMethod = HttpMethod.GET; @SuppressWarnings("deprecation") - Set acceptTypes = Set.of(MediaType.APPLICATION_JSON_UTF8); + Set acceptTypes = Set.of(MediaType.APPLICATION_JSON); MediaType contentType = MediaType.APPLICATION_JSON; HttpHeaders httpHeaders = new HttpHeaders(); @Nullable diff --git a/testify-spring/src/main/java/io/github/finoid/testify/spring/http/package-info.java b/testify-spring/src/main/java/io/github/finoid/testify/spring/http/package-info.java new file mode 100644 index 0000000..6c56d6f --- /dev/null +++ b/testify-spring/src/main/java/io/github/finoid/testify/spring/http/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package io.github.finoid.testify.spring.http; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/testify-spring/src/main/java/io/github/finoid/testify/spring/http/servlet/MockMvcBuilder.java b/testify-spring/src/main/java/io/github/finoid/testify/spring/http/servlet/MockMvcBuilder.java index f8fafe8..56bad48 100644 --- a/testify-spring/src/main/java/io/github/finoid/testify/spring/http/servlet/MockMvcBuilder.java +++ b/testify-spring/src/main/java/io/github/finoid/testify/spring/http/servlet/MockMvcBuilder.java @@ -13,11 +13,12 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.mock.web.MockServletContext; import org.springframework.test.web.servlet.setup.AbstractMockMvcBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder; +import org.springframework.util.PathMatcher; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.StringValueResolver; import org.springframework.validation.Validator; +import org.springframework.web.accept.ApiVersionStrategy; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationObjectSupport; @@ -61,8 +62,7 @@ public class MockMvcBuilder extends AbstractMockMvcBuilder { private final List controllers; - @Nullable - private List controllerAdvice; + private @Nullable List controllerAdvice; private List> messageConverters = new ArrayList<>(); @@ -72,40 +72,29 @@ public class MockMvcBuilder extends AbstractMockMvcBuilder mappedInterceptors = new ArrayList<>(); - @Nullable - private Validator validator; + private @Nullable Validator validator; - @Nullable - private ContentNegotiationManager contentNegotiationManager; + private @Nullable ContentNegotiationManager contentNegotiationManager; - @Nullable - private FormattingConversionService conversionService; + private @Nullable FormattingConversionService conversionService; - @Nullable - private List handlerExceptionResolvers; + private @Nullable ApiVersionStrategy versionStrategy; - @Nullable - private Long asyncRequestTimeout; + private @Nullable List handlerExceptionResolvers; - @Nullable - private List viewResolvers; + private @Nullable Long asyncRequestTimeout; + + private @Nullable List viewResolvers; private LocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); - @Nullable - private FlashMapManager flashMapManager; + private @Nullable FlashMapManager flashMapManager; private boolean preferPathMatcher = false; - @Nullable - private PathPatternParser patternParser; - - private boolean useSuffixPatternMatch = false; + private @Nullable PathPatternParser patternParser; - private boolean useTrailingSlashPatternMatch = true; - - @Nullable - private Boolean removeSemicolonContent; + private @Nullable Boolean removeSemicolonContent; private final Map placeholderValues = new HashMap<>(); @@ -116,7 +105,7 @@ public class MockMvcBuilder extends AbstractMockMvcBuilder instantiateIfNecessary(Object[] specified) { * * @since 4.2 */ - public MockMvcBuilder setControllerAdvice(Object... controllerAdvice) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setControllerAdvice(Object... controllerAdvice) { this.controllerAdvice = instantiateIfNecessary(controllerAdvice); return this; } @@ -150,7 +139,7 @@ public MockMvcBuilder setControllerAdvice(Object... controllerAdvice) { * and response. If no message converters are added to the list, a default * list of converters is added instead. */ - public MockMvcBuilder setMessageConverters(HttpMessageConverter... messageConverters) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setMessageConverters(HttpMessageConverter... messageConverters) { this.messageConverters = Arrays.asList(messageConverters); return this; } @@ -160,7 +149,7 @@ public MockMvcBuilder setMessageConverters(HttpMessageConverter... messageCon * The default implementation used, assuming JSR-303 is on the classpath, is * {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean}. */ - public MockMvcBuilder setValidator(Validator validator) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setValidator(Validator validator) { this.validator = validator; return this; } @@ -169,15 +158,25 @@ public MockMvcBuilder setValidator(Validator validator) { * Provide a conversion service with custom formatters and converters. * If not set, a {@link DefaultFormattingConversionService} is used by default. */ - public MockMvcBuilder setConversionService(FormattingConversionService conversionService) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setConversionService(FormattingConversionService conversionService) { this.conversionService = conversionService; return this; } + /** + * Set the {@link ApiVersionStrategy} to use when mapping requests. + * + * @since 7.0 + */ + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setApiVersionStrategy(@Nullable ApiVersionStrategy versionStrategy) { + this.versionStrategy = versionStrategy; + return this; + } + /** * Add interceptors mapped to all incoming requests. */ - public MockMvcBuilder addInterceptors(HandlerInterceptor... interceptors) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder addInterceptors(HandlerInterceptor... interceptors) { addMappedInterceptors(null, interceptors); return this; } @@ -185,8 +184,8 @@ public MockMvcBuilder addInterceptors(HandlerInterceptor... interceptors) { /** * Add interceptors mapped to a set of path patterns. */ - public MockMvcBuilder addMappedInterceptors( - @Nullable String[] pathPatterns, HandlerInterceptor... interceptors) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder addMappedInterceptors( + String @Nullable [] pathPatterns, HandlerInterceptor... interceptors) { for (HandlerInterceptor interceptor : interceptors) { this.mappedInterceptors.add(new MappedInterceptor(pathPatterns, null, interceptor)); @@ -197,7 +196,7 @@ public MockMvcBuilder addMappedInterceptors( /** * Set a ContentNegotiationManager. */ - public MockMvcBuilder setContentNegotiationManager(ContentNegotiationManager manager) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setContentNegotiationManager(ContentNegotiationManager manager) { this.contentNegotiationManager = manager; return this; } @@ -209,7 +208,7 @@ public MockMvcBuilder setContentNegotiationManager(ContentNegotiationManager man * * @param timeout the timeout value in milliseconds */ - public MockMvcBuilder setAsyncRequestTimeout(long timeout) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setAsyncRequestTimeout(long timeout) { this.asyncRequestTimeout = timeout; return this; } @@ -217,7 +216,7 @@ public MockMvcBuilder setAsyncRequestTimeout(long timeout) { /** * Provide custom resolvers for controller method arguments. */ - public MockMvcBuilder setCustomArgumentResolvers(HandlerMethodArgumentResolver... argumentResolvers) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setCustomArgumentResolvers(HandlerMethodArgumentResolver... argumentResolvers) { this.customArgumentResolvers = Arrays.asList(argumentResolvers); return this; } @@ -225,7 +224,7 @@ public MockMvcBuilder setCustomArgumentResolvers(HandlerMethodArgumentResolver.. /** * Provide custom handlers for controller method return values. */ - public MockMvcBuilder setCustomReturnValueHandlers(HandlerMethodReturnValueHandler... handlers) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setCustomReturnValueHandlers(HandlerMethodReturnValueHandler... handlers) { this.customReturnValueHandlers = Arrays.asList(handlers); return this; } @@ -233,7 +232,7 @@ public MockMvcBuilder setCustomReturnValueHandlers(HandlerMethodReturnValueHandl /** * Set the HandlerExceptionResolver types to use as a list. */ - public MockMvcBuilder setHandlerExceptionResolvers(List exceptionResolvers) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setHandlerExceptionResolvers(List exceptionResolvers) { this.handlerExceptionResolvers = exceptionResolvers; return this; } @@ -241,7 +240,7 @@ public MockMvcBuilder setHandlerExceptionResolvers(ListsingletonList(new StaticViewResolver(view)); + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setSingleView(View view) { + this.viewResolvers = Collections.singletonList(new io.github.finoid.testify.spring.http.servlet.MockMvcBuilder.StaticViewResolver(view)); return this; } @@ -269,7 +268,7 @@ public MockMvcBuilder setSingleView(View view) { * Provide a LocaleResolver instance. * If not provided, the default one used is {@link AcceptHeaderLocaleResolver}. */ - public MockMvcBuilder setLocaleResolver(LocaleResolver localeResolver) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setLocaleResolver(LocaleResolver localeResolver) { this.localeResolver = localeResolver; return this; } @@ -278,57 +277,36 @@ public MockMvcBuilder setLocaleResolver(LocaleResolver localeResolver) { * Provide a custom FlashMapManager instance. * If not provided, {@code SessionFlashMapManager} is used by default. */ - public MockMvcBuilder setFlashMapManager(FlashMapManager flashMapManager) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setFlashMapManager(FlashMapManager flashMapManager) { this.flashMapManager = flashMapManager; return this; } /** - * Enable URL path matching with parsed - * {@link org.springframework.web.util.pattern.PathPattern PathPatterns} - * instead of String pattern matching with a {@link org.springframework.util.PathMatcher}. + * Configure the parser to use for + * {@link org.springframework.web.util.pattern.PathPattern PathPatterns}. + *

By default, this is a default instance of {@link PathPatternParser}. * * @param parser the parser to use * @since 5.3 */ - public MockMvcBuilder setPatternParser(@Nullable PathPatternParser parser) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setPatternParser(@Nullable PathPatternParser parser) { this.patternParser = parser; this.preferPathMatcher = (this.patternParser == null); return this; } - /** - * Whether to use suffix pattern match (".*") when matching patterns to - * requests. If enabled a method mapped to "/users" also matches to "/users.*". - *

The default value is {@code false}. - * - * @deprecated as of 5.2.4. See class-level note in {@link RequestMappingHandlerMapping} on the deprecation of path extension config options - */ - @Deprecated - public MockMvcBuilder setUseSuffixPatternMatch(boolean useSuffixPatternMatch) { - this.useSuffixPatternMatch = useSuffixPatternMatch; - this.preferPathMatcher |= useSuffixPatternMatch; - return this; - } - - /** - * Whether to match to URLs irrespective of the presence of a trailing slash. - * If enabled a method mapped to "/users" also matches to "/users/". - * - * @deprecated as of 6.0, see {@link PathPatternParser#setMatchOptionalTrailingSeparator(boolean)} - */ - @Deprecated(since = "6.0") - public MockMvcBuilder setUseTrailingSlashPatternMatch(boolean useTrailingSlashPatternMatch) { - this.useTrailingSlashPatternMatch = useTrailingSlashPatternMatch; - return this; - } - /** * Set if ";" (semicolon) content should be stripped from the request URI. The value, * if provided, is in turn set on * {@link org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean)}. + * + * @deprecated use of {@link PathMatcher} and {@link UrlPathHelper} is deprecated + * for use at runtime in web modules in favor of parsed patterns with + * {@link PathPatternParser}. */ - public MockMvcBuilder setRemoveSemicolonContent(boolean removeSemicolonContent) { + @Deprecated(since = "7.0", forRemoval = true) + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setRemoveSemicolonContent(boolean removeSemicolonContent) { this.removeSemicolonContent = removeSemicolonContent; return this; } @@ -341,7 +319,7 @@ public MockMvcBuilder setRemoveSemicolonContent(boolean removeSemicolonContent) * * @since 4.2.8 */ - public MockMvcBuilder addPlaceholderValue(String name, String value) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder addPlaceholderValue(String name, String value) { this.placeholderValues.put(name, value); return this; } @@ -352,7 +330,7 @@ public MockMvcBuilder addPlaceholderValue(String name, String value) { * @param factory the factory * @since 5.0 */ - public MockMvcBuilder setCustomHandlerMapping(Supplier factory) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setCustomHandlerMapping(Supplier factory) { this.handlerMappingFactory = factory; return this; } @@ -363,7 +341,7 @@ public MockMvcBuilder setCustomHandlerMapping(Supplier additionalBeans) { + public io.github.finoid.testify.spring.http.servlet.MockMvcBuilder setAdditionalBeans(List additionalBeans) { this.additionalBeans = additionalBeans; return this; } @@ -379,7 +357,8 @@ protected WebApplicationContext initWebAppContext() { @SuppressWarnings("deprecation") private void registerMvcSingletons(StubWebApplicationContext wac) { - StandaloneConfiguration config = new StandaloneConfiguration(); + io.github.finoid.testify.spring.http.servlet.MockMvcBuilder.StandaloneConfiguration + config = new io.github.finoid.testify.spring.http.servlet.MockMvcBuilder.StandaloneConfiguration(); config.setApplicationContext(wac); wac.addBeans(this.controllers); @@ -422,8 +401,6 @@ private void registerMvcSingletons(StubWebApplicationContext wac) { wac.addBeans(initViewResolvers(wac)); wac.addBean(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME, this.localeResolver); - wac.addBean(DispatcherServlet.THEME_RESOLVER_BEAN_NAME, - new org.springframework.web.servlet.theme.FixedThemeResolver()); wac.addBean(DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, new DefaultRequestToViewNameTranslator()); @@ -461,21 +438,22 @@ protected Map extendMvcSingletons(@Nullable ServletContext servl return Collections.emptyMap(); } + /** * Using the MVC Java configuration as the starting point for the "standalone" setup. */ private class StandaloneConfiguration extends WebMvcConfigurationSupport { - @SuppressWarnings("deprecation") + @SuppressWarnings("removal") public RequestMappingHandlerMapping getHandlerMapping( FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { RequestMappingHandlerMapping handlerMapping = handlerMappingFactory.get(); - handlerMapping.setEmbeddedValueResolver(new StaticStringValueResolver(placeholderValues)); + handlerMapping.setEmbeddedValueResolver( + new io.github.finoid.testify.spring.http.servlet.MockMvcBuilder.StaticStringValueResolver(placeholderValues)); if (patternParser == null && preferPathMatcher) { handlerMapping.setPatternParser(null); - handlerMapping.setUseSuffixPatternMatch(useSuffixPatternMatch); if (removeSemicolonContent != null) { UrlPathHelper pathHelper = new UrlPathHelper(); pathHelper.setRemoveSemicolonContent(removeSemicolonContent); @@ -484,13 +462,16 @@ public RequestMappingHandlerMapping getHandlerMapping( } else if (patternParser != null) { handlerMapping.setPatternParser(patternParser); } - handlerMapping.setUseTrailingSlashMatch(useTrailingSlashPatternMatch); + if (versionStrategy != null) { + handlerMapping.setApiVersionStrategy(versionStrategy); + } handlerMapping.setOrder(0); handlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); return handlerMapping; } @Override + @SuppressWarnings("removal") protected void configureMessageConverters(List> converters) { converters.addAll(messageConverters); } @@ -589,19 +570,15 @@ public String resolveStringValue(String strVal) throws BeansException { } } - /** - * A {@link ViewResolver} that always returns same View. - */ private static class StaticViewResolver implements ViewResolver { private final View view; - public StaticViewResolver(View view) { + public StaticViewResolver(final View view) { this.view = view; } @Override - @Nullable - public View resolveViewName(String viewName, Locale locale) { + public @Nullable View resolveViewName(final String viewName, final Locale locale) { return this.view; } } diff --git a/testify-spring/src/main/java/io/github/finoid/testify/spring/http/servlet/StubWebApplicationContext.java b/testify-spring/src/main/java/io/github/finoid/testify/spring/http/servlet/StubWebApplicationContext.java index b92b18f..0d50628 100644 --- a/testify-spring/src/main/java/io/github/finoid/testify/spring/http/servlet/StubWebApplicationContext.java +++ b/testify-spring/src/main/java/io/github/finoid/testify/spring/http/servlet/StubWebApplicationContext.java @@ -19,6 +19,7 @@ import org.springframework.context.MessageSourceResolvable; import org.springframework.context.NoSuchMessageException; import org.springframework.context.support.DelegatingMessageSource; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ResolvableType; import org.springframework.core.env.Environment; import org.springframework.core.env.StandardEnvironment; @@ -46,8 +47,8 @@ class StubWebApplicationContext implements WebApplicationContext { private final ServletContext servletContext; - private final StubBeanFactory - beanFactory = new StubBeanFactory(); + private final io.github.finoid.testify.spring.http.servlet.StubWebApplicationContext.StubBeanFactory + beanFactory = new io.github.finoid.testify.spring.http.servlet.StubWebApplicationContext.StubBeanFactory(); private final String id = ObjectUtils.identityToString(this); @@ -104,8 +105,7 @@ public long getStartupDate() { } @Override - @Nullable - public ApplicationContext getParent() { + public @Nullable ApplicationContext getParent() { return null; } @@ -142,7 +142,7 @@ public T getBean(String name, Class requiredType) throws BeansException { } @Override - public Object getBean(String name, Object... args) throws BeansException { + public Object getBean(String name, @Nullable Object @Nullable ... args) throws BeansException { return this.beanFactory.getBean(name, args); } @@ -152,7 +152,7 @@ public T getBean(Class requiredType) throws BeansException { } @Override - public T getBean(Class requiredType, Object... args) throws BeansException { + public T getBean(Class requiredType, @Nullable Object @Nullable ... args) throws BeansException { return this.beanFactory.getBean(requiredType, args); } @@ -166,6 +166,11 @@ public ObjectProvider getBeanProvider(ResolvableType requiredType) { return this.beanFactory.getBeanProvider(requiredType); } + @Override + public ObjectProvider getBeanProvider(ParameterizedTypeReference requiredType) { + return this.beanFactory.getBeanProvider(requiredType); + } + @Override public boolean containsBean(String name) { return this.beanFactory.containsBean(name); @@ -192,14 +197,12 @@ public boolean isTypeMatch(String name, Class typeToMatch) throws NoSuchBeanD } @Override - @Nullable - public Class getType(String name) throws NoSuchBeanDefinitionException { + public @Nullable Class getType(String name) throws NoSuchBeanDefinitionException { return this.beanFactory.getType(name); } @Override - @Nullable - public Class getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException { + public @Nullable Class getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException { return this.beanFactory.getType(name, allowFactoryBeanInit); } @@ -282,16 +285,14 @@ public Map getBeansWithAnnotation(Class an } @Override - @Nullable - public A findAnnotationOnBean(String beanName, Class annotationType) + public @Nullable A findAnnotationOnBean(String beanName, Class annotationType) throws NoSuchBeanDefinitionException { return this.beanFactory.findAnnotationOnBean(beanName, annotationType); } @Override - @Nullable - public A findAnnotationOnBean( + public @Nullable A findAnnotationOnBean( String beanName, Class annotationType, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException { @@ -311,8 +312,7 @@ public Set findAllAnnotationsOnBean( //--------------------------------------------------------------------- @Override - @Nullable - public BeanFactory getParentBeanFactory() { + public @Nullable BeanFactory getParentBeanFactory() { return null; } @@ -326,18 +326,17 @@ public boolean containsLocalBean(String name) { //--------------------------------------------------------------------- @Override - @Nullable - public String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) { + public @Nullable String getMessage(String code, Object @Nullable [] args, @Nullable String defaultMessage, @Nullable Locale locale) { return this.messageSource.getMessage(code, args, defaultMessage, locale); } @Override - public String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException { + public String getMessage(String code, Object @Nullable [] args, @Nullable Locale locale) throws NoSuchMessageException { return this.messageSource.getMessage(code, args, locale); } @Override - public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException { + public String getMessage(MessageSourceResolvable resolvable, @Nullable Locale locale) throws NoSuchMessageException { return this.messageSource.getMessage(resolvable, locale); } @@ -346,8 +345,7 @@ public String getMessage(MessageSourceResolvable resolvable, Locale locale) thro //--------------------------------------------------------------------- @Override - @Nullable - public ClassLoader getClassLoader() { + public @Nullable ClassLoader getClassLoader() { return ClassUtils.getDefaultClassLoader(); } @@ -383,7 +381,7 @@ private class StubBeanFactory extends StaticListableBeanFactory implements Autow @Override public Object initializeBean(Object existingBean, String beanName) throws BeansException { if (existingBean instanceof ApplicationContextAware applicationContextAware) { - applicationContextAware.setApplicationContext(StubWebApplicationContext.this); + applicationContextAware.setApplicationContext(io.github.finoid.testify.spring.http.servlet.StubWebApplicationContext.this); } return existingBean; } @@ -393,7 +391,7 @@ public T createBean(Class beanClass) { return BeanUtils.instantiateClass(beanClass); } - @Deprecated + @Deprecated(since = "6.1") @Override public Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) { return BeanUtils.instantiateClass(beanClass); @@ -428,15 +426,13 @@ public Object resolveBeanByName(String name, DependencyDescriptor descriptor) th } @Override - @Nullable - public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName) { + public @Nullable Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName) { throw new UnsupportedOperationException("Dependency resolution not supported"); } @Override - @Nullable - public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, - @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) { + public @Nullable Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, + @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) { throw new UnsupportedOperationException("Dependency resolution not supported"); } diff --git a/testify-spring/src/main/java/io/github/finoid/testify/spring/type/ApiTest.java b/testify-spring/src/main/java/io/github/finoid/testify/spring/type/ApiTest.java index 57be61e..cd0a7b5 100644 --- a/testify-spring/src/main/java/io/github/finoid/testify/spring/type/ApiTest.java +++ b/testify-spring/src/main/java/io/github/finoid/testify/spring/type/ApiTest.java @@ -1,6 +1,5 @@ package io.github.finoid.testify.spring.type; -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.finoid.testify.spring.http.HttpAsserter.HttpAsserterDsl; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ExtendWith; @@ -11,6 +10,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.test.context.junit.jupiter.SpringExtension; +import tools.jackson.databind.ObjectMapper; /** * Base test class for API-related tests, providing utility methods for HTTP assertions and component setup. diff --git a/testify-spring/src/main/java/io/github/finoid/testify/spring/type/EndToEndHttpTest.java b/testify-spring/src/main/java/io/github/finoid/testify/spring/type/EndToEndHttpTest.java index 2d53c62..e031cd4 100644 --- a/testify-spring/src/main/java/io/github/finoid/testify/spring/type/EndToEndHttpTest.java +++ b/testify-spring/src/main/java/io/github/finoid/testify/spring/type/EndToEndHttpTest.java @@ -1,6 +1,5 @@ package io.github.finoid.testify.spring.type; -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.finoid.testify.snapshot.SnapshotterExtension; import io.github.finoid.testify.spring.http.HttpAsserter; import org.junit.jupiter.api.Tag; @@ -8,12 +7,13 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.test.web.servlet.MockMvc; +import tools.jackson.databind.ObjectMapper; /** * Base test class for E2E-related tests, providing utility methods for HTTP assertions and component setup. diff --git a/testify-spring/src/main/java/io/github/finoid/testify/spring/type/package-info.java b/testify-spring/src/main/java/io/github/finoid/testify/spring/type/package-info.java new file mode 100644 index 0000000..6755c64 --- /dev/null +++ b/testify-spring/src/main/java/io/github/finoid/testify/spring/type/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package io.github.finoid.testify.spring.type; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/testify-spring/src/test/java/io/github/finoid/testify/spring/http/wiremock/WireMockUtilsIntegrationTest.java b/testify-spring/src/test/java/io/github/finoid/testify/spring/http/wiremock/WireMockUtilsIntegrationTest.java index de0d79b..5efaaa7 100644 --- a/testify-spring/src/test/java/io/github/finoid/testify/spring/http/wiremock/WireMockUtilsIntegrationTest.java +++ b/testify-spring/src/test/java/io/github/finoid/testify/spring/http/wiremock/WireMockUtilsIntegrationTest.java @@ -9,10 +9,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.test.context.ContextConfiguration; @@ -21,7 +20,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -@ContextConfiguration(classes = {JacksonAutoConfiguration.class, CodecsAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, +@ContextConfiguration(classes = {JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, RestClientAutoConfiguration.class, WireMockUtilsIntegrationTest.CustomTestConfiguration.class}) class WireMockUtilsIntegrationTest extends IntegrationTest { @RegisterExtension diff --git a/testify-spring/src/test/java/io/github/finoid/testify/spring/type/CustomApiTest.java b/testify-spring/src/test/java/io/github/finoid/testify/spring/type/CustomApiTest.java index 13296b2..b194463 100644 --- a/testify-spring/src/test/java/io/github/finoid/testify/spring/type/CustomApiTest.java +++ b/testify-spring/src/test/java/io/github/finoid/testify/spring/type/CustomApiTest.java @@ -1,6 +1,5 @@ package io.github.finoid.testify.spring.type; -import com.fasterxml.jackson.core.type.TypeReference; import io.github.finoid.testify.spring.http.HttpAsserter; import io.github.finoid.testify.spring.http.HttpAsserter.RequestSpec; import org.junit.jupiter.api.Assertions; @@ -9,6 +8,7 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import tools.jackson.core.type.TypeReference; class CustomApiTest extends ApiTest { @Autowired diff --git a/testify-spring/src/test/java/io/github/finoid/testify/spring/type/CustomEndToEndHttpTest.java b/testify-spring/src/test/java/io/github/finoid/testify/spring/type/CustomEndToEndHttpTest.java index 1dc05d7..ebfc8e4 100644 --- a/testify-spring/src/test/java/io/github/finoid/testify/spring/type/CustomEndToEndHttpTest.java +++ b/testify-spring/src/test/java/io/github/finoid/testify/spring/type/CustomEndToEndHttpTest.java @@ -1,6 +1,5 @@ package io.github.finoid.testify.spring.type; -import com.fasterxml.jackson.core.type.TypeReference; import io.github.finoid.testify.spring.http.HttpAsserter; import io.github.finoid.testify.spring.http.HttpAsserter.RequestSpec; import org.junit.jupiter.api.Assertions; @@ -12,6 +11,7 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import tools.jackson.core.type.TypeReference; @Import(CustomEndToEndHttpTest.ExampleController.class) class CustomEndToEndHttpTest extends EndToEndHttpTest { diff --git a/testify-testcontainers/pom.xml b/testify-testcontainers/pom.xml index 4bcea2e..c7ec7d8 100644 --- a/testify-testcontainers/pom.xml +++ b/testify-testcontainers/pom.xml @@ -49,15 +49,15 @@ org.testcontainers - mongodb + testcontainers-mongodb org.testcontainers - mssqlserver + testcontainers-mssqlserver org.testcontainers - postgresql + testcontainers-postgresql org.projectlombok diff --git a/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/Statement.java b/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/Statement.java deleted file mode 100644 index 66bfd29..0000000 --- a/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/Statement.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.junit.runners.model; - -// Temporary fake interface until https://github.com/testcontainers/testcontainers-java/issues/970 has been fixed -@SuppressWarnings("unused") -public interface Statement { - -} \ No newline at end of file diff --git a/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/TestRule.java b/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/TestRule.java deleted file mode 100644 index 93fe0d6..0000000 --- a/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/TestRule.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.junit.rules; - -// Temporary fake interface until https://github.com/testcontainers/testcontainers-java/issues/970 has been fixed -@SuppressWarnings("unused") -public interface TestRule { - -} \ No newline at end of file diff --git a/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/extensions/MSSQLServerContainerExtension.java b/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/extensions/MSSQLServerContainerExtension.java index 7d2725a..2e289d5 100644 --- a/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/extensions/MSSQLServerContainerExtension.java +++ b/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/extensions/MSSQLServerContainerExtension.java @@ -12,7 +12,7 @@ import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import org.testcontainers.containers.MSSQLServerContainer; +import org.testcontainers.mssqlserver.MSSQLServerContainer; import org.testcontainers.utility.DockerImageName; import java.util.Map; @@ -34,7 +34,7 @@ public class MSSQLServerContainerExtension implements BeforeAllCallback, AfterAllCallback { private final Configuration configuration; @Nullable - private volatile MSSQLServerContainer container; + private volatile MSSQLServerContainer container; private MSSQLServerContainerExtension() { this(Configuration.defaultConfiguration()); @@ -60,7 +60,7 @@ public void beforeAll(final ExtensionContext context) { synchronized (this) { if (container == null) { - this.container = new MSSQLServerContainer<>(configuration.dockerImage) + this.container = new MSSQLServerContainer(configuration.dockerImage) .acceptLicense() .withPassword(configuration.password) .withCreateContainerCmdModifier(cmd -> cmd.withHostConfig( @@ -78,7 +78,7 @@ public void beforeAll(final ExtensionContext context) { @Override public void afterAll(final ExtensionContext context) throws Exception { // Stop only if not reusing, otherwise keep the container for performance purpose - final MSSQLServerContainer container = this.container; + final MSSQLServerContainer container = this.container; if (container != null && container.isRunning() && !configuration.reuse) { container.stop(); } @@ -92,7 +92,7 @@ public void afterAll(final ExtensionContext context) throws Exception { */ @SuppressWarnings("resource") public String getHost() { - final MSSQLServerContainer container = ensureInitialized(); + final MSSQLServerContainer container = ensureInitialized(); return container.getHost(); } @@ -105,7 +105,7 @@ public String getHost() { */ @SuppressWarnings("resource") public int getPort() { - final MSSQLServerContainer container = ensureInitialized(); + final MSSQLServerContainer container = ensureInitialized(); return container.getFirstMappedPort(); } @@ -118,7 +118,7 @@ public int getPort() { */ @SuppressWarnings("resource") public String getJdbcUrl() { - final MSSQLServerContainer container = ensureInitialized(); + final MSSQLServerContainer container = ensureInitialized(); return container.getJdbcUrl(); } @@ -131,7 +131,7 @@ public String getJdbcUrl() { */ @SuppressWarnings("resource") public String getUsername() { - final MSSQLServerContainer container = ensureInitialized(); + final MSSQLServerContainer container = ensureInitialized(); return container.getUsername(); } @@ -143,7 +143,7 @@ public String getUsername() { */ @SuppressWarnings("resource") public String getPassword() { - final MSSQLServerContainer container = ensureInitialized(); + final MSSQLServerContainer container = ensureInitialized(); return container.getPassword(); } @@ -177,8 +177,8 @@ public Map toSpringDatasourceProperties() { ); } - private MSSQLServerContainer ensureInitialized() { - final MSSQLServerContainer container = this.container; + private MSSQLServerContainer ensureInitialized() { + final MSSQLServerContainer container = this.container; if (container == null) { throw new IllegalStateException("MSSQLServerContainer is not created yet. Did you register the extension and let it start?"); } diff --git a/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/extensions/PostgresContainerExtension.java b/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/extensions/PostgresContainerExtension.java index d636def..5aa8e10 100644 --- a/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/extensions/PostgresContainerExtension.java +++ b/testify-testcontainers/src/main/java/io/github/finoid/testify/testcontainers/extensions/PostgresContainerExtension.java @@ -12,7 +12,7 @@ import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.postgresql.PostgreSQLContainer; import org.testcontainers.utility.DockerImageName; import java.util.Map; @@ -29,12 +29,12 @@ * .reuse(true) * ); * }; - *} + * } */ public class PostgresContainerExtension implements BeforeAllCallback, AfterAllCallback { private final Configuration configuration; @Nullable - private volatile PostgreSQLContainer container; + private volatile PostgreSQLContainer container; private PostgresContainerExtension() { this(Configuration.defaultConfiguration()); @@ -52,7 +52,6 @@ private PostgresContainerExtension(final Configuration configuration) { * @param context The context in which the given test is executed. */ @Override - @SuppressWarnings("resource") public void beforeAll(final ExtensionContext context) { if (container != null && container.isRunning()) { return; // Already started @@ -61,7 +60,7 @@ public void beforeAll(final ExtensionContext context) { synchronized (this) { if (container == null) { this.container = - new PostgreSQLContainer<>(configuration.dockerImage) + new PostgreSQLContainer(configuration.dockerImage) .withCreateContainerCmdModifier(cmd -> cmd.withHostConfig( HostConfig.newHostConfig() .withPortBindings( @@ -78,7 +77,7 @@ public void beforeAll(final ExtensionContext context) { @Override public void afterAll(final ExtensionContext context) { // Stop only if not reusing, otherwise keep the container for performance purpose - final PostgreSQLContainer container = this.container; + final PostgreSQLContainer container = this.container; if (container != null && container.isRunning() && !configuration.reuse) { container.stop(); } @@ -92,7 +91,7 @@ public void afterAll(final ExtensionContext context) { */ @SuppressWarnings("resource") public String getHost() { - final PostgreSQLContainer container = ensureInitialized(); + final PostgreSQLContainer container = ensureInitialized(); return container.getHost(); } @@ -105,7 +104,7 @@ public String getHost() { */ @SuppressWarnings("resource") public int getPort() { - final PostgreSQLContainer container = ensureInitialized(); + final PostgreSQLContainer container = ensureInitialized(); return container.getFirstMappedPort(); } @@ -118,7 +117,7 @@ public int getPort() { */ @SuppressWarnings("resource") public String getJdbcUrl() { - final PostgreSQLContainer container = ensureInitialized(); + final PostgreSQLContainer container = ensureInitialized(); return container.getJdbcUrl(); } @@ -131,7 +130,7 @@ public String getJdbcUrl() { */ @SuppressWarnings("resource") public String getUsername() { - final PostgreSQLContainer container = ensureInitialized(); + final PostgreSQLContainer container = ensureInitialized(); return container.getUsername(); } @@ -143,7 +142,7 @@ public String getUsername() { */ @SuppressWarnings("resource") public String getPassword() { - final PostgreSQLContainer container = ensureInitialized(); + final PostgreSQLContainer container = ensureInitialized(); return container.getPassword(); } @@ -195,8 +194,8 @@ public static PostgresContainerExtension create(final Configuration configuratio return new PostgresContainerExtension(configuration); } - private PostgreSQLContainer ensureInitialized() { - final PostgreSQLContainer container = this.container; + private PostgreSQLContainer ensureInitialized() { + final PostgreSQLContainer container = this.container; if (container == null) { throw new IllegalStateException("PostgreSQLContainer is not created yet. Did you register the extension and let it start?"); }