From e790828e194d0663d5a55a63d6b85be087998442 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 23 Oct 2020 11:55:48 -0700 Subject: [PATCH] Allow TestPropertyValues.of to take a Map source Extend the API of `TestPropertyValues` so that it can be constructed from an existing `Map` or a `Stream` and mapping `Function`. Closes gh-23685 --- .../boot/test/util/TestPropertyValues.java | 113 ++++++++++++++++-- .../test/util/TestPropertyValuesTests.java | 67 +++++++++++ 2 files changed, 167 insertions(+), 13 deletions(-) diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java index 5695fb884e3..2f182318fd4 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java @@ -23,6 +23,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; +import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -59,17 +60,62 @@ public final class TestPropertyValues { } /** - * Builder method to add more properties. + * Return a new {@link TestPropertyValues} instance with additional entries. + * Name-value pairs can be specified with colon (":") or equals ("=") separators. * @param pairs the property pairs to add * @return a new {@link TestPropertyValues} instance */ public TestPropertyValues and(String... pairs) { - return and(Arrays.stream(pairs).map(Pair::parse)); + return and(Arrays.stream(pairs), Pair::parse); } - private TestPropertyValues and(Stream pairs) { + /** + * Return a new {@link TestPropertyValues} instance with additional entries. + * Name-value pairs can be specified with colon (":") or equals ("=") separators. + * @param pairs the property pairs to add + * @return a new {@link TestPropertyValues} instance + * @since 2.4.0 + */ + public TestPropertyValues and(Iterable pairs) { + return (pairs != null) ? and(StreamSupport.stream(pairs.spliterator(), false)) : this; + } + + /** + * Return a new {@link TestPropertyValues} instance with additional entries. + * Name-value pairs can be specified with colon (":") or equals ("=") separators. + * @param pairs the property pairs to add + * @return a new {@link TestPropertyValues} instance + * @since 2.4.0 + */ + public TestPropertyValues and(Stream pairs) { + return (pairs != null) ? and(pairs, Pair::parse) : this; + } + + /** + * Return a new {@link TestPropertyValues} instance with additional entries. + * @param map the map of properties that need to be added to the environment + * @return a new {@link TestPropertyValues} instance + * @since 2.4.0 + */ + public TestPropertyValues and(Map map) { + return (map != null) ? and(map.entrySet().stream(), Pair::fromMapEntry) : this; + } + + /** + * Return a new {@link TestPropertyValues} instance with additional entries. + * @param the stream element type + * @param stream the elements that need to be added to the environment + * @param mapper a mapper function to convert an element from the stream into a + * {@link Pair} + * @return a new {@link TestPropertyValues} instance + * @since 2.4.0 + */ + public TestPropertyValues and(Stream stream, Function mapper) { + if (stream == null) { + return this; + } Map properties = new LinkedHashMap<>(this.properties); - pairs.filter(Objects::nonNull).forEach((pair) -> pair.addTo(properties)); + stream.map(mapper).filter(Objects::nonNull).forEach((pair) -> pair.addTo(properties)); return new TestPropertyValues(properties); } @@ -174,10 +220,7 @@ public final class TestPropertyValues { * @return the new instance */ public static TestPropertyValues of(Iterable pairs) { - if (pairs == null) { - return empty(); - } - return of(StreamSupport.stream(pairs.spliterator(), false)); + return (pairs != null) ? of(StreamSupport.stream(pairs.spliterator(), false)) : empty(); } /** @@ -189,10 +232,30 @@ public final class TestPropertyValues { * @return the new instance */ public static TestPropertyValues of(Stream pairs) { - if (pairs == null) { - return empty(); - } - return empty().and(pairs.map(Pair::parse)); + return (pairs != null) ? of(pairs, Pair::parse) : empty(); + } + + /** + * Return a new {@link TestPropertyValues} with the underlying map populated with the + * given map entries. + * @param map the map of properties that need to be added to the environment + * @return the new instance + */ + public static TestPropertyValues of(Map map) { + return (map != null) ? of(map.entrySet().stream(), Pair::fromMapEntry) : empty(); + } + + /** + * Return a new {@link TestPropertyValues} with the underlying map populated with the + * given stream. + * @param the stream element type + * @param stream the elements that need to be added to the environment + * @param mapper a mapper function to convert an element from the stream into a + * {@link Pair} + * @return the new instance + */ + public static TestPropertyValues of(Stream stream, Function mapper) { + return (stream != null) ? empty().and(stream, mapper) : empty(); } /** @@ -247,6 +310,13 @@ public final class TestPropertyValues { private String value; + /** + * Create a new {@link Pair} instance. + * @param name the name + * @param value the value + * @deprecated since 2.4.0 in favor of {@link #of(String, String)} + */ + @Deprecated public Pair(String name, String value) { Assert.hasLength(name, "Name must not be empty"); this.name = name; @@ -276,7 +346,24 @@ public final class TestPropertyValues { return Math.min(colonIndex, equalIndex); } - private static Pair of(String name, String value) { + /** + * Factory method to create a {@link Pair} from a {@code Map.Entry}. + * @param entry the map entry + * @return the {@link Pair} instance or {@code null} + * @since 2.4.0 + */ + public static Pair fromMapEntry(Map.Entry entry) { + return (entry != null) ? of(entry.getKey(), entry.getValue()) : null; + } + + /** + * Factory method to create a {@link Pair} from a name and value. + * @param name the name + * @param value the value + * @return the {@link Pair} instance or {@code null} + * @since 2.4.0 + */ + public static Pair of(String name, String value) { if (StringUtils.hasLength(name) || StringUtils.hasLength(value)) { return new Pair(name, value); } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/util/TestPropertyValuesTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/util/TestPropertyValuesTests.java index d99b49a068a..a3f6bbbb889 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/util/TestPropertyValuesTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/util/TestPropertyValuesTests.java @@ -16,8 +16,14 @@ package org.springframework.boot.test.util; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Stream; + import org.junit.jupiter.api.Test; +import org.springframework.boot.test.util.TestPropertyValues.Pair; import org.springframework.boot.test.util.TestPropertyValues.Type; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertySource; @@ -25,6 +31,7 @@ import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.SystemEnvironmentPropertySource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; /** * Tests for {@link TestPropertyValues}. @@ -36,6 +43,47 @@ class TestPropertyValuesTests { private final ConfigurableEnvironment environment = new StandardEnvironment(); + @Test + void ofStringArrayCreatesValues() { + TestPropertyValues.of("spring:boot", "version:latest").applyTo(this.environment); + assertThat(this.environment.getProperty("spring")).isEqualTo("boot"); + assertThat(this.environment.getProperty("version")).isEqualTo("latest"); + } + + @Test + void ofIterableCreatesValues() { + TestPropertyValues.of(Arrays.asList("spring:boot", "version:latest")).applyTo(this.environment); + assertThat(this.environment.getProperty("spring")).isEqualTo("boot"); + assertThat(this.environment.getProperty("version")).isEqualTo("latest"); + } + + @Test + void ofStreamCreatesValues() { + TestPropertyValues.of(Stream.of("spring:boot", "version:latest")).applyTo(this.environment); + assertThat(this.environment.getProperty("spring")).isEqualTo("boot"); + assertThat(this.environment.getProperty("version")).isEqualTo("latest"); + } + + @Test + void ofMapCreatesValues() { + Map map = new LinkedHashMap<>(); + map.put("spring", "boot"); + map.put("version", "latest"); + TestPropertyValues.of(map).applyTo(this.environment); + assertThat(this.environment.getProperty("spring")).isEqualTo("boot"); + assertThat(this.environment.getProperty("version")).isEqualTo("latest"); + } + + @Test + void ofMappedStreamCreatesValues() { + TestPropertyValues.of(Stream.of("spring|boot", "version|latest"), (string) -> { + String[] split = string.split("\\|"); + return Pair.of(split[0], split[1]); + }).applyTo(this.environment); + assertThat(this.environment.getProperty("spring")).isEqualTo("boot"); + assertThat(this.environment.getProperty("version")).isEqualTo("latest"); + } + @Test void applyToEnvironmentShouldAttachConfigurationPropertySource() { TestPropertyValues.of("foo.bar=baz").applyTo(this.environment); @@ -133,4 +181,23 @@ class TestPropertyValuesTests { } } + @Test + void pairOfCreatesPair() { + Map map = new LinkedHashMap<>(); + Pair.of("spring", "boot").addTo(map); + assertThat(map).containsOnly(entry("spring", "boot")); + } + + @Test + void pairOfWhenNameAndValueAreEmptyReturnsNull() { + assertThat(Pair.of("", "")).isNull(); + } + + @Test + void pairFromMapEntryCreatesPair() { + Map map = new LinkedHashMap<>(); + Pair.fromMapEntry(entry("spring", "boot")).addTo(map); + assertThat(map).containsOnly(entry("spring", "boot")); + } + }