ProjectBuilder Test for VersionCatalog

Hi,

I’m using version catalog feature in one of my project where I published my catalog to artifactory and in another project (lets say consumer project) I’m importing that catalog.

I have some test cases using ProjectBuilder and some with GradleRunner (Gradle Test Kit). When I run the ProjectBuilder test cases it is giving an error

org.gradle.api.UnknownDomainObjectException: Extension of type 'VersionCatalogsExtension' does not exist. Currently registered extension types: [ExtraPropertiesExtension, SpringBootExtension, StandardDependencyManagementExtension, ProjectOperations, AsciidoctorJExtension, OpenApiExtension, OpenApi3Extension, PostmanExtension, BasePluginExtension, DefaultArtifactPublicationSet, SourceSetContainer, ReportingExtension, JavaToolchainService, JavaPluginExtension, TestingExtension, ContractVerifierExtension]
	at org.gradle.internal.extensibility.ExtensionsStorage.getHolderByType(ExtensionsStorage.java:89)
	at org.gradle.internal.extensibility.ExtensionsStorage.getByType(ExtensionsStorage.java:74)
	at org.gradle.internal.extensibility.DefaultConvention.getByType(DefaultConvention.java:172)
	at org.gradle.internal.extensibility.DefaultConvention.getByType(DefaultConvention.java:167)

Here is how my custom precompiled plugin is :

CustomPlugin.kt

class CustomPlugin: Plugin<Project> {

          override fun apply(project: Project) {
                  project.applyDependencies()
           }

           private fun Project.applyDependencies() {
                  val dependencyHandler = project.dependencies
                 val versionCatalog = 
                          project.extensions.getByType(VersionCatalogsExtension::class.java).named("libs")

                 using dependencyHandler.add("implementation" , versionCatalog.findLibrary("springframework-cloud").get().get())
                 ..... // some more dependencies
           }

}

CustomPluginTest.kt

class CustomPluginTest {

    @Test
    fun testDependencies() { 
         val project = ProjectBuilder.builder().build()
    }
}

Any help is much appreciated.

Thanks,
Sudheer.

I do mock the extension.
For example with Spock like

project.extensions.add('versionCatalogs', Stub(VersionCatalogsExtension) {
   find('libs') >> Optional.of(Stub(VersionCatalog) {
      findVersion('java') >> Optional.of(Stub(VersionConstraint) {
         requiredVersion >> '4.2.3'
      })
   })
})

Thank you very much @Vampire !

1 Like

Sorry for revisiting again.

When I tested using spock framework, I was getting the same exception and it is throwing near project.pluginManager.apply("example-plugin") we tried mock after this line.

given:
        def project = ProjectBuilder.builder().withProjectDir(File("src/test/resources/example-default")).build()
        project.pluginManager.apply("example-plugin")

        def springCloudDependenciesVersion = "2023.0.1"

        project.extensions.add("versionCatalog", Stub(VersionCatalogsExtension) {
            find("libs") >> Optional.of(Stub(VersionCatalog) {
                findVersion("springframework-cloud") >> Optional.of(Stub(VersionConstraint) {
                    requiredVersion >> springCloudDependenciesVersion
                })
                findLibrary("spring-cloud-dependencies") >> Optional.of(Stub(DependencyConstraint) {
                    group >> "org.springframework.cloud"
                    name >> "spring-cloud-dependencies"
                    version >> springCloudDependenciesVersion
                })
            })
        })



        expect:
        project.pluginManager.hasPlugin("org.springframework.boot") == true

Sure, of course you have to create the extension before you call the apply, as your plugin is trying to access the version catalog in its apply method.

That’s like accessing a field before assigning a value to it and then wondering why you get a NullPointerException. :wink:

Thank you @Vampire !

I think I was able to solve this finally (hopefully :slight_smile:) . I’m using Junit5 only, so added mockito-kotlin dependency to keep my changes minimal. Spock I was not using, but first tried to see how to implement based on your input and then tried to find equivalent in mockito-kotlin.

Dependencies I used

implementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
implementation("com.moandjiezana.toml:toml4j:0.7.2")
@Test
fun testPlugin() {

            val project = ProjectBuilder.builder().build()

            val filePath = this::class.java.classLoader.getResource("version-catalog/libs.versions.toml").file
            val versions = readVersionsFromTomlFile(filePath)
            val libraries = readLibrariesFromTomlFile(filePath, versions)

            val versionCatalog = createMockVersionCatalog(versions, project, libraries)
            val versionCatalogsExtension = createMockVersionCatalogsExtension(versionCatalog, "libs")

            project.extensions.add(VersionCatalogsExtension::class.java, "libs", versionCatalogsExtension)
            project.pluginManager.apply("example-plugin")

            val implementationDependencies = project.configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).dependencies
            val hasSpringCloudDependency = implementationDependencies.any { dependency ->
                dependency.group == "org.springframework.cloud" && dependency.name == "spring-cloud-dependencies"
            }

            assertEquals(true, hasSpringCloudDependency)

            val assertPlugins = listOf("org.springframework.boot", "io.spring.dependency-management", "org.asciidoctor.jvm.convert", "com.epages.restdocs-api-spec", "org.springframework.cloud.contract")
            assertPlugins.forEach { plugin ->
                assertEquals(true, project.pluginManager.hasPlugin(plugin))
            }

        }

Here are the helper method

private fun createMockVersionCatalog(versions: Map<String, String>, project: Project, libraries: Map<String, MinimalExternalModuleDependency>): VersionCatalog = mock<VersionCatalog> {
        on { findVersion(any()) }.thenAnswer { invocation ->
            val requestedVersionName = invocation.arguments[0] as String
            val version = versions[requestedVersionName]
            Optional.ofNullable(version?.let {
                mock<VersionConstraint> { on { requiredVersion }.thenReturn(version) }
            })
        }
        on { findLibrary(any()) }.thenAnswer { invocation ->
            val requestedLibraryName = invocation.arguments[0] as String
            libraries[requestedLibraryName]?.let { dependency -> Optional.of(project.provider { dependency }) }
        }
    }

    private fun createMockVersionCatalogsExtension(versionCatalog: VersionCatalog, name: String): VersionCatalogsExtension = mock {
        on { named(name) }.thenReturn(versionCatalog)
    }

    fun readVersionsFromTomlFile(filePath: String): Map<String, String> {
        val toml = Toml().read(File(filePath))
        val versionsTable = toml.getTable("versions").toMap()

        return versionsTable.mapValues { (_, value) -> value as String }
    }

    fun readLibrariesFromTomlFile(filePath: String, versions: Map<String, String>): Map<String, MinimalExternalModuleDependency> {
        val toml = Toml().read(File(filePath))
        val librariesTable = toml.getTable("libraries").toMap()

        return librariesTable.map { (libraryName, libraryToml) ->
            @Suppress("UNCHECKED_CAST")
            val libraryTable = libraryToml as Map<String, Any>
            val group = libraryTable["group"] as String
            val name = libraryTable["name"] as String
            val versionRef = libraryTable["version_ref"] as String
            val version = versions[versionRef]

            val dependency: MinimalExternalModuleDependency = mock {
                on { this.group }.thenReturn(group)
                on { this.name }.thenReturn(name)
                on { this.version }.thenReturn(version)
            }
            libraryName to dependency
        }.toMap()
    }

Thank you very much again for the prompt help and suggestions !

-Sudheer.

1 Like