diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/ResourceContent.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/ResourceContent.java new file mode 100644 index 00000000000..85a81e3aa39 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/ResourceContent.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testsupport.classpath.resources; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that indicates that the content of a resource should be injected. Supported + * on parameters of type: + * + *
+ * For cases where one resource needs to refer to another, the resource's content may
+ * contain the placeholder ${resourceRoot}. It will be replaced with the path
+ * to the root of the resources. For example, a resource with the {@link #name}
+ * {@code example.txt} can be referenced using ${resourceRoot}/example.txt.
+ *
+ * @author Andy Wilkinson
+ */
+@Inherited
+@Repeatable(WithResources.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+@ExtendWith(ResourcesExtension.class)
+public @interface WithResource {
+
+ /**
+ * The name of the resource.
+ * @return the name
+ */
+ String name();
+
+ /**
+ * The content of the resource. When omitted an empty resource will be created.
+ * @return the content
+ */
+ String content() default "";
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/WithResourceDirectories.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/WithResourceDirectories.java
new file mode 100644
index 00000000000..973931c65c2
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/WithResourceDirectories.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Container annotation for {@link WithResourceDirectory}.
+ *
+ * @author Andy Wilkinson
+ */
+@Inherited
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface WithResourceDirectories {
+
+ WithResourceDirectory[] value();
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/WithResourceDirectory.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/WithResourceDirectory.java
new file mode 100644
index 00000000000..95fb2ef7585
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/WithResourceDirectory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Makes a resource available from the thread context class loader. Typically used when a
+ * test requires an empty directory to exist.
+ *
+ * @author Andy Wilkinson
+ */
+@Inherited
+@Repeatable(WithResourceDirectories.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+@ExtendWith(ResourcesExtension.class)
+public @interface WithResourceDirectory {
+
+ /**
+ * The name of the directory.
+ * @return the name
+ */
+ String value();
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/WithResources.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/WithResources.java
new file mode 100644
index 00000000000..1f9b72e767b
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/WithResources.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Container annotation for {@link WithResource}.
+ *
+ * @author Andy Wilkinson
+ */
+@Inherited
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface WithResources {
+
+ WithResource[] value();
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/package-info.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/package-info.java
new file mode 100644
index 00000000000..ddc2e5c98ec
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/resources/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Custom JUnit extension for testing with resources.
+ */
+package org.springframework.boot.testsupport.classpath.resources;
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnClassWithPackageResourcesTests.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnClassWithPackageResourcesTests.java
new file mode 100644
index 00000000000..31949514396
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnClassWithPackageResourcesTests.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.io.ClassPathResource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link WithPackageResources} on a class.
+ *
+ * @author Andy Wilkinson
+ */
+@WithPackageResources({ "resource-1.txt", "resource-2.txt", "sub/resource-3.txt" })
+class OnClassWithPackageResourcesTests {
+
+ @Test
+ void whenWithPackageResourcesIsUsedOnAClassThenResourcesAreAvailable() throws IOException {
+ assertThat(new ClassPathResource("resource-1.txt").getContentAsString(StandardCharsets.UTF_8)).isEqualTo("one");
+ assertThat(new ClassPathResource("resource-2.txt").getContentAsString(StandardCharsets.UTF_8)).isEqualTo("two");
+ assertThat(new ClassPathResource("sub/resource-3.txt").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("three");
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnClassWithResourceTests.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnClassWithResourceTests.java
new file mode 100644
index 00000000000..a1c701e166c
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnClassWithResourceTests.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.io.ClassPathResource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link WithResource} when used on a class.
+ *
+ * @author Andy Wilkinson
+ */
+@WithResource(name = "on-class", content = "class content")
+class OnClassWithResourceTests {
+
+ @Test
+ void whenWithResourceIsUsedOnAClassThenResourceIsAvailable() throws IOException {
+ assertThat(new ClassPathResource("on-class").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("class content");
+ }
+
+ @Test
+ @WithResource(name = "method-resource", content = "method")
+ void whenWithResourceIsUsedOnClassAndMethodThenBothResourcesAreAvailable() throws IOException {
+ assertThat(new ClassPathResource("on-class").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("class content");
+ assertThat(new ClassPathResource("method-resource").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("method");
+ }
+
+ @Test
+ @WithResource(name = "method-resource-1", content = "method-1")
+ @WithResource(name = "method-resource-2", content = "method-2")
+ void whenWithResourceIsUsedOnClassAndRepeatedOnMethodThenAllResourcesAreAvailable() throws IOException {
+ assertThat(new ClassPathResource("on-class").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("class content");
+ assertThat(new ClassPathResource("method-resource-1").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("method-1");
+ assertThat(new ClassPathResource("method-resource-2").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("method-2");
+ }
+
+ @Nested
+ class NestedTests {
+
+ @Test
+ void whenWithResourceIsUsedOnEnclosingClassThenResourceIsAvailable() throws IOException {
+ assertThat(new ClassPathResource("on-class").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("class content");
+ }
+
+ }
+
+ @Nested
+ @WithResource(name = "on-nested-class", content = "nested class content")
+ class WithResourceNestedTests {
+
+ @Test
+ void whenWithResourceIsUsedOnEnclosingClassAndClassThenBothResourcesAreAvailable() throws IOException {
+ assertThat(new ClassPathResource("on-class").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("class content");
+ assertThat(new ClassPathResource("on-nested-class").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("nested class content");
+ }
+
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnSuperClassWithPackageResourcesTests.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnSuperClassWithPackageResourcesTests.java
new file mode 100644
index 00000000000..2a3b3739a09
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnSuperClassWithPackageResourcesTests.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.io.ClassPathResource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link WithPackageResources} when used on a super-class.
+ *
+ * @author Andy Wilkinson
+ */
+class OnSuperClassWithPackageResourcesTests extends WithPackageResourcesClass {
+
+ @Test
+ void whenWithPackageResourcesIsUsedOnASuperClassThenResourcesAreAvailable() throws IOException {
+ assertThat(new ClassPathResource("resource-1.txt").getContentAsString(StandardCharsets.UTF_8)).isEqualTo("one");
+ assertThat(new ClassPathResource("resource-2.txt").getContentAsString(StandardCharsets.UTF_8)).isEqualTo("two");
+ assertThat(new ClassPathResource("sub/resource-3.txt").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("three");
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnSuperClassWithResourceTests.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnSuperClassWithResourceTests.java
new file mode 100644
index 00000000000..2e47735fb14
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/OnSuperClassWithResourceTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.io.ClassPathResource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link WithResource} when used on a super-class.
+ *
+ * @author Andy Wilkinson
+ */
+class OnSuperClassWithResourceTests extends WithResourceClass {
+
+ @Test
+ void whenWithResourceIsUsedOnASuperClassThenResourceIsAvailable() throws IOException {
+ assertThat(new ClassPathResource("on-super-class").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("super-class content");
+ }
+
+ @Test
+ @WithResource(name = "method-resource", content = "method")
+ void whenWithResourceIsUsedOnASuperClassAndMethodThenBothResourcesAreAvailable() throws IOException {
+ assertThat(new ClassPathResource("on-super-class").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("super-class content");
+ assertThat(new ClassPathResource("method-resource").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("method");
+ }
+
+ @Test
+ @WithResource(name = "method-resource-1", content = "method-1")
+ @WithResource(name = "method-resource-2", content = "method-2")
+ void whenWithResourceIsUsedOnASuperClassAndRepeatedOnMethodThenAllResourcesAreAvailable() throws IOException {
+ assertThat(new ClassPathResource("on-super-class").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("super-class content");
+ assertThat(new ClassPathResource("method-resource-1").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("method-1");
+ assertThat(new ClassPathResource("method-resource-2").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("method-2");
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/ResourcesTests.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/ResourcesTests.java
new file mode 100644
index 00000000000..c91d508c68e
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/ResourcesTests.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+
+/**
+ * Tests for {@link Resources}.
+ *
+ * @author Andy Wilkinson
+ */
+class ResourcesTests {
+
+ @TempDir
+ private Path root;
+
+ @Test
+ void whenAddResourceThenResourceIsCreated() {
+ new Resources(this.root).addResource("test", "test-content");
+ assertThat(this.root.resolve("test")).hasContent("test-content");
+ }
+
+ @Test
+ void whenAddResourceHasContentReferencingResourceRootThenResourceIsCreatedWithReferenceToRoot() {
+ new Resources(this.root).addResource("test", "*** ${resourceRoot} ***");
+ assertThat(this.root.resolve("test")).hasContent("*** " + this.root + " ***");
+ }
+
+ @Test
+ void whenAddResourceWithPathThenResourceIsCreated() {
+ new Resources(this.root).addResource("a/b/c/test", "test-content");
+ assertThat(this.root.resolve("a/b/c/test")).hasContent("test-content");
+ }
+
+ @Test
+ void whenAddResourceAndResourceAlreadyExistsThenResourcesIsOverwritten() {
+ Resources resources = new Resources(this.root);
+ resources.addResource("a/b/c/test", "original-content");
+ resources.addResource("a/b/c/test", "new-content");
+ assertThat(this.root.resolve("a/b/c/test")).hasContent("new-content");
+ }
+
+ @Test
+ void whenAddPackageThenNamedResourcesFromPackageAreCreated() {
+ new Resources(this.root).addPackage(getClass().getPackage(),
+ new String[] { "resource-1.txt", "sub/resource-3.txt" });
+ assertThat(this.root.resolve("resource-1.txt")).hasContent("one");
+ assertThat(this.root.resolve("resource-2.txt")).doesNotExist();
+ assertThat(this.root.resolve("sub/resource-3.txt")).hasContent("three");
+ }
+
+ @Test
+ void whenAddResourceAndDeleteThenResourceDoesNotExist() {
+ Resources resources = new Resources(this.root);
+ resources.addResource("test", "test-content");
+ assertThat(this.root.resolve("test")).hasContent("test-content");
+ resources.delete();
+ assertThat(this.root.resolve("test")).doesNotExist();
+ }
+
+ @Test
+ void whenAddPackageAndDeleteThenResourcesDoNotExist() {
+ Resources resources = new Resources(this.root);
+ resources.addPackage(getClass().getPackage(),
+ new String[] { "resource-1.txt", "resource-2.txt", "sub/resource-3.txt" });
+ assertThat(this.root.resolve("resource-1.txt")).hasContent("one");
+ assertThat(this.root.resolve("resource-2.txt")).hasContent("two");
+ assertThat(this.root.resolve("sub/resource-3.txt")).hasContent("three");
+ resources.delete();
+ assertThat(this.root.resolve("resource-1.txt")).doesNotExist();
+ assertThat(this.root.resolve("resource-2.txt")).doesNotExist();
+ assertThat(this.root.resolve("sub/resource-3.txt")).doesNotExist();
+ assertThat(this.root.resolve("sub")).doesNotExist();
+ }
+
+ @Test
+ void whenAddDirectoryThenDirectoryIsCreated() {
+ Resources resources = new Resources(this.root);
+ resources.addDirectory("dir");
+ assertThat(this.root.resolve("dir")).isDirectory();
+ }
+
+ @Test
+ void whenAddDirectoryWithPathThenDirectoryIsCreated() {
+ Resources resources = new Resources(this.root);
+ resources.addDirectory("one/two/three/dir");
+ assertThat(this.root.resolve("one/two/three/dir")).isDirectory();
+ }
+
+ @Test
+ void whenAddDirectoryAndDirectoryAlreadyExistsThenDoesNotThrow() {
+ Resources resources = new Resources(this.root);
+ resources.addDirectory("one/two/three/dir");
+ resources.addDirectory("one/two/three/dir");
+ assertThat(this.root.resolve("one/two/three/dir")).isDirectory();
+ }
+
+ @Test
+ void whenAddDirectoryAndResourceAlreadyExistsThenIllegalStateExceptionIsThrown() {
+ Resources resources = new Resources(this.root);
+ resources.addResource("one/two/three/", "content");
+ assertThatIllegalStateException().isThrownBy(() -> resources.addDirectory("one/two/three"));
+ }
+
+ @Test
+ void whenAddResourceAndDirectoryAlreadyExistsThenIllegalStateExceptionIsThrown() {
+ Resources resources = new Resources(this.root);
+ resources.addDirectory("one/two/three");
+ assertThatIllegalStateException().isThrownBy(() -> resources.addResource("one/two/three", "content"));
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithPackageResourcesClass.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithPackageResourcesClass.java
new file mode 100644
index 00000000000..754e48c8fb7
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithPackageResourcesClass.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+/**
+ * Class to test the use of {@link WithPackageResources} on a super-class.
+ *
+ * @author Andy Wilkinson
+ */
+@WithPackageResources({ "resource-1.txt", "resource-2.txt", "sub/resource-3.txt" })
+class WithPackageResourcesClass {
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithPackageResourcesTests.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithPackageResourcesTests.java
new file mode 100644
index 00000000000..8d5f425b30c
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithPackageResourcesTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.io.ClassPathResource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link WithPackageResources}.
+ *
+ * @author Andy Wilkinson
+ */
+class WithPackageResourcesTests {
+
+ @Test
+ @WithPackageResources({ "resource-1.txt", "resource-2.txt", "sub/resource-3.txt" })
+ void whenWithPackageResourcesIsUsedOnAMethodThenResourcesAreAvailable() throws IOException {
+ assertThat(new ClassPathResource("resource-1.txt").getContentAsString(StandardCharsets.UTF_8)).isEqualTo("one");
+ assertThat(new ClassPathResource("resource-2.txt").getContentAsString(StandardCharsets.UTF_8)).isEqualTo("two");
+ assertThat(new ClassPathResource("sub/resource-3.txt").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("three");
+ }
+
+ @Test
+ @WithPackageResources("sub/resource-3.txt")
+ void whenWithPackageResourcesOnlyIncludesSomeResourcesThenOnlyIncludedResourcesAreAvailable() throws IOException {
+ assertThat(new ClassPathResource("resource-1.txt").exists()).isFalse();
+ assertThat(new ClassPathResource("resource-2.txt").exists()).isFalse();
+ assertThat(new ClassPathResource("sub/resource-3.txt").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("three");
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithResourceClass.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithResourceClass.java
new file mode 100644
index 00000000000..0b386bbd5fb
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithResourceClass.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+/**
+ * Class to test the use of {@link WithResource} on a super-class
+ *
+ * @author Andy Wilkinson
+ */
+@WithResource(name = "on-super-class", content = "super-class content")
+class WithResourceClass {
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithResourceDirectoryTests.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithResourceDirectoryTests.java
new file mode 100644
index 00000000000..7e3d29a4de6
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithResourceDirectoryTests.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.io.ClassPathResource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link WithResourceDirectory}.
+ *
+ * @author Andy Wilkinson
+ */
+class WithResourceDirectoryTests {
+
+ @Test
+ @WithResourceDirectory("test")
+ void whenWithResourceDirectoryIsUsedOnAMethodThenDirectoryIsCreated() throws IOException {
+ assertThat(new ClassPathResource("test").getFile()).isDirectory();
+ }
+
+ @Test
+ @WithResourceDirectory("com/example/nested")
+ void whenWithResourceDirectoryNamesANestedDirectoryThenDirectoryIsCreated() throws IOException {
+ assertThat(new ClassPathResource("com/example/nested").getFile()).isDirectory();
+ }
+
+ @Test
+ @WithResourceDirectory("1")
+ @WithResourceDirectory("2")
+ @WithResourceDirectory("3")
+ void whenWithResourceDirectoryIsRepeatedOnAMethodThenAllResourceDirectoriesAreCreated() throws IOException {
+ assertThat(new ClassPathResource("1").getFile()).isDirectory();
+ assertThat(new ClassPathResource("2").getFile()).isDirectory();
+ assertThat(new ClassPathResource("3").getFile()).isDirectory();
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithResourceTests.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithResourceTests.java
new file mode 100644
index 00000000000..87e15b16c41
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/resources/WithResourceTests.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.testsupport.classpath.resources;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.io.ClassPathResource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link WithResource}.
+ *
+ * @author Andy Wilkinson
+ */
+class WithResourceTests {
+
+ @Test
+ @WithResource(name = "test", content = "content")
+ void whenWithResourceIsUsedOnAMethodThenResourceIsAvailable() throws IOException {
+ assertThat(new ClassPathResource("test").getContentAsString(StandardCharsets.UTF_8)).isEqualTo("content");
+ }
+
+ @Test
+ @WithResource(name = "test", content = "content")
+ void whenWithResourceIsUsedOnAMethodThenResourceIsAvailableFromFileResourcesRoot(@ResourcesRoot File root) {
+ assertThat(new File(root, "test")).hasContent("content");
+ }
+
+ @Test
+ @WithResource(name = "test", content = "content")
+ void whenWithResourceIsUsedOnAMethodThenResourceIsAvailableFromPathResourcesRoot(@ResourcesRoot Path root) {
+ assertThat(root.resolve("test")).hasContent("content");
+ }
+
+ @Test
+ @WithResource(name = "test", content = "content")
+ void whenWithResourceIsUsedOnAMethodThenResourceIsAvailableFromPathResourcePath(
+ @ResourcePath("test") Path resource) {
+ assertThat(resource).hasContent("content");
+ }
+
+ @Test
+ @WithResource(name = "test", content = "content")
+ void whenWithResourceIsUsedOnAMethodThenResourceIsAvailableFromFileResourcePath(
+ @ResourcePath("test") File resource) {
+ assertThat(resource).hasContent("content");
+ }
+
+ @Test
+ @WithResource(name = "test", content = "content")
+ void whenWithResourceIsUsedOnAMethodThenResourceIsAvailableFromStringResourcePath(
+ @ResourcePath("test") String resource) {
+ assertThat(new File(resource)).hasContent("content");
+ }
+
+ @Test
+ @WithResource(name = "test", content = "content")
+ void whenWithResourceIsUsedOnAMethodThenResourceContentIsAvailableAsAString(
+ @ResourceContent("test") String content) {
+ assertThat(content).isEqualTo("content");
+ }
+
+ @Test
+ @WithResource(name = "com/example/test-resource", content = "content")
+ void whenWithResourceNameIncludesADirectoryThenResourceIsAvailable() throws IOException {
+ assertThat(new ClassPathResource("com/example/test-resource").getContentAsString(StandardCharsets.UTF_8))
+ .isEqualTo("content");
+ }
+
+ @Test
+ @WithResource(name = "1", content = "one")
+ @WithResource(name = "2", content = "two")
+ @WithResource(name = "3", content = "three")
+ void whenWithResourceIsRepeatedOnAMethodThenAllResourcesAreAvailable() throws IOException {
+ assertThat(new ClassPathResource("1").getContentAsString(StandardCharsets.UTF_8)).isEqualTo("one");
+ assertThat(new ClassPathResource("2").getContentAsString(StandardCharsets.UTF_8)).isEqualTo("two");
+ assertThat(new ClassPathResource("3").getContentAsString(StandardCharsets.UTF_8)).isEqualTo("three");
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/resources/org/springframework/boot/testsupport/classpath/resources/resource-1.txt b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/resources/org/springframework/boot/testsupport/classpath/resources/resource-1.txt
new file mode 100644
index 00000000000..43dd47ea691
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/resources/org/springframework/boot/testsupport/classpath/resources/resource-1.txt
@@ -0,0 +1 @@
+one
\ No newline at end of file
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/resources/org/springframework/boot/testsupport/classpath/resources/resource-2.txt b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/resources/org/springframework/boot/testsupport/classpath/resources/resource-2.txt
new file mode 100644
index 00000000000..64c5e5885a4
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/resources/org/springframework/boot/testsupport/classpath/resources/resource-2.txt
@@ -0,0 +1 @@
+two
\ No newline at end of file
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/resources/org/springframework/boot/testsupport/classpath/resources/sub/resource-3.txt b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/resources/org/springframework/boot/testsupport/classpath/resources/sub/resource-3.txt
new file mode 100644
index 00000000000..1d19714ffbc
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/resources/org/springframework/boot/testsupport/classpath/resources/sub/resource-3.txt
@@ -0,0 +1 @@
+three
\ No newline at end of file