diff --git a/spring-boot-test-dbunit-integration-tests/src/test/kotlin/io/camassia/spring/dbunit/DatabaseSetupAnnotationTest.kt b/spring-boot-test-dbunit-integration-tests/src/test/kotlin/io/camassia/spring/dbunit/DatabaseSetupAnnotationTest.kt index 50e5f91..9235e61 100644 --- a/spring-boot-test-dbunit-integration-tests/src/test/kotlin/io/camassia/spring/dbunit/DatabaseSetupAnnotationTest.kt +++ b/spring-boot-test-dbunit-integration-tests/src/test/kotlin/io/camassia/spring/dbunit/DatabaseSetupAnnotationTest.kt @@ -352,6 +352,47 @@ class DatabaseSetupAnnotationTest : RepositoryTest() { } } + @Nested + @DatabaseSetup(tables = [ + Table("demo1", + Row(Cell("id", "1"), Cell("name", "Test1")) + ) + ]) + inner class WithMultipleLevelsOfAnnotations { + @Nested + @DatabaseSetup(tables = [ + Table("demo1", + Row(Cell("id", "2"), Cell("name", "Test2")) + ) + ], operation = DatabaseOperation.INSERT) + inner class LevelTwo { + @Nested + @DatabaseSetup(tables = [ + Table("demo1", + Row(Cell("id", "3"), Cell("name", "Test3")) + ) + ], operation = DatabaseOperation.INSERT) + inner class LevelTwo { + @Test + @DatabaseSetup(tables = [ + Table("demo1", + Row(Cell("id", "4"), Cell("name", "Test4")) + ) + ], operation = DatabaseOperation.INSERT) + fun `should combine multiple levels of annotations`() { + val result = selectAllFrom("demo1") + assertThat(result).hasSize(4) + assertThat(result).containsExactly( + 1L to "Test1", + 2L to "Test2", + 3L to "Test3", + 4L to "Test4", + ) + } + } + } + } + @TestConfiguration class DemoTestConfiguration { @@ -373,4 +414,4 @@ class DatabaseSetupAnnotationTest : RepositoryTest() { @Bean fun defaults() = TableDefaults("demo1", io.camassia.spring.dbunit.api.dataset.Cell("name", "default-name")) } -} \ No newline at end of file +} diff --git a/spring-boot-test-dbunit/src/main/kotlin/io/camassia/spring/dbunit/api/NestedClassUtil.kt b/spring-boot-test-dbunit/src/main/kotlin/io/camassia/spring/dbunit/api/NestedClassUtil.kt new file mode 100644 index 0000000..29948b4 --- /dev/null +++ b/spring-boot-test-dbunit/src/main/kotlin/io/camassia/spring/dbunit/api/NestedClassUtil.kt @@ -0,0 +1,34 @@ +package io.camassia.spring.dbunit.api + +import org.slf4j.LoggerFactory + +object NestedClassUtil { + + private val log = LoggerFactory.getLogger(NestedClassUtil::class.java) + + fun getHierarchy(clazz: Class<*>): List> { + val hierarchy = mutableListOf>() + val builder = StringBuilder() + val className = clazz.name + for (i in className.indices) { + val char = className[i] + if (char == '$' || i == className.length) { + safelyFetchClass(builder.toString())?.also { + hierarchy.add(it) + } + } + builder.append(char) + } + safelyFetchClass(builder.toString())?.also { + hierarchy.add(it) + } + return hierarchy + } + + private fun safelyFetchClass(name: String): Class<*>? = try { + Class.forName(name) + } catch (throwable: ClassNotFoundException) { + log.warn("Invalid classname [$name]. Please avoid using '$' within class names.") + null + } +} diff --git a/spring-boot-test-dbunit/src/main/kotlin/io/camassia/spring/dbunit/api/wiring/DatabaseSetupAndTeardownTestExecutionListener.kt b/spring-boot-test-dbunit/src/main/kotlin/io/camassia/spring/dbunit/api/wiring/DatabaseSetupAndTeardownTestExecutionListener.kt index b030302..286a9a7 100644 --- a/spring-boot-test-dbunit/src/main/kotlin/io/camassia/spring/dbunit/api/wiring/DatabaseSetupAndTeardownTestExecutionListener.kt +++ b/spring-boot-test-dbunit/src/main/kotlin/io/camassia/spring/dbunit/api/wiring/DatabaseSetupAndTeardownTestExecutionListener.kt @@ -2,6 +2,7 @@ package io.camassia.spring.dbunit.api.wiring import io.camassia.spring.dbunit.api.DatabaseTester import io.camassia.spring.dbunit.api.DbUnitException +import io.camassia.spring.dbunit.api.NestedClassUtil import io.camassia.spring.dbunit.api.annotations.DatabaseSetup import io.camassia.spring.dbunit.api.annotations.DatabaseTeardown import io.camassia.spring.dbunit.api.customization.DatabaseOperation @@ -93,7 +94,7 @@ class DatabaseSetupAndTeardownTestExecutionListener : TestExecutionListener, Ord } ) - private fun TestContext.annotations() = (this.testClass.annotations + this.testMethod.annotations) + private fun TestContext.annotations(): List = NestedClassUtil.getHierarchy(this.testClass).flatMap { it.annotations.toList() } + this.testMethod.annotations.toList() private fun TestContext.dbUnit() = applicationContext.getBean(DatabaseTester::class.java) private fun String.toArray() = this.takeIf { it.isNotEmpty() }?.let { arrayOf(it) } ?: emptyArray() } diff --git a/spring-boot-test-dbunit/src/test/kotlin/io/camassia/spring/dbunit/api/NestedClassUtilTest.kt b/spring-boot-test-dbunit/src/test/kotlin/io/camassia/spring/dbunit/api/NestedClassUtilTest.kt new file mode 100644 index 0000000..4356de4 --- /dev/null +++ b/spring-boot-test-dbunit/src/test/kotlin/io/camassia/spring/dbunit/api/NestedClassUtilTest.kt @@ -0,0 +1,60 @@ +package io.camassia.spring.dbunit.api + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +internal class NestedClassUtilTest { + + @Nested + inner class GetHierarchy { + @Test + fun shouldExtractZeroLevelHierarchy() { + val hierarchy = NestedClassUtil.getHierarchy(NestedClassUtilTest::class.java) + + assertThat(hierarchy).hasSize(1).containsExactly(NestedClassUtilTest::class.java) + } + + @Test + fun shouldExtractOneLevelHierarchy() { + val hierarchy = NestedClassUtil.getHierarchy(LevelOne::class.java) + + assertThat(hierarchy).hasSize(2).containsExactly( + NestedClassUtilTest::class.java, + LevelOne::class.java + ) + } + + @Test + fun shouldExtractTwoLevelHierarchy() { + val hierarchy = NestedClassUtil.getHierarchy(LevelOne.LevelTwo::class.java) + + assertThat(hierarchy).hasSize(3).containsExactly( + NestedClassUtilTest::class.java, + LevelOne::class.java, + LevelOne.LevelTwo::class.java + ) + } + + @Test + fun shouldExtractOneLevelHierarchy_withSpecialName() { + val hierarchy = NestedClassUtil.getHierarchy(`Special $ name`::class.java) + + assertThat(hierarchy).hasSize(2).containsExactly( + NestedClassUtilTest::class.java, + `Special $ name`::class.java + ) + } + } + + + + @Nested + inner class LevelOne { + @Nested + inner class LevelTwo + } + + @Nested + inner class `Special $ name` +}