Use jenkins-test-harness + spock to unit test jenkins job dsl script

Hello all,

currently i try to create a project to centralize the generation of job on my jenkins : i use jenkins job dsl 1.77 and try to follow this explanation : Testing DSL Scripts · jenkinsci/job-dsl-plugin Wiki · GitHub

my env is : gradle 8 and jdk 11 on unix env (ubuntu)

here i follow the code :

// build.gradle
plugins {
    id 'groovy'
}

sourceSets {
    jobs {
        groovy {
            srcDirs 'src/jobs'
            compileClasspath += main.compileClasspath
        }
        compileClasspath += sourceSets.main.output
        runtimeClasspath += sourceSets.main.output
    }
    src {
        groovy {
            srcDir 'src/main/groovy'
        }
    }
    test {
        groovy {
            srcDir 'src/test/groovy'
        }
    }
}

repositories {
    mavenCentral()
    maven { url 'https://repo.jenkins-ci.org/releases/' }
    jcenter{url "https://jcenter.bintray.com/"}
}

configurations {
    testPlugins {}

    // see JENKINS-45512
    testCompile {
        exclude group: 'xalan'
        exclude group: 'xerces'
    }
}

configurations.all*.exclude group: 'xalan'

dependencies {
    implementation 'org.codehaus.groovy:groovy:2.5.14'
    implementation ("org.jenkins-ci.plugins:job-dsl-core:${jobDslVersion}") {
        exclude module: 'xstream'
    }


    testCompile 'org.spockframework:spock-core:1.3-groovy-2.5'
    testImplementation 'org.spockframework:spock-core:2.0-M5-groovy-3.0'
    testImplementation 'cglib:cglib-nodep:2.2.2' // used by Spock

    // Jenkins test harness dependencies
    testImplementation('org.jenkins-ci.main:jenkins-test-harness:2146.v6b_f8b_1cb_d12d') { //2.34 2146.v6b_f8b_1cb_d12d
        exclude group: 'org.netbeans.modules', module: 'org-netbeans-insane'
    }
    testImplementation("org.jenkins-ci.main:jenkins-war:${jenkinsVersion}") {
        exclude group: 'org.jenkins-ci.ui', module: 'bootstrap'
    }
    testCompile("org.jenkins-ci.main:jenkins-war:${jenkinsVersion}") {
        exclude group: 'org.jenkins-ci.ui', module: 'bootstrap'
    }

    // Job DSL plugin including plugin dependencies
    testImplementation "org.jenkins-ci.plugins:job-dsl:${jobDslVersion}"
    testImplementation "org.jenkins-ci.plugins:job-dsl:${jobDslVersion}@jar"
    testImplementation 'org.jenkins-ci.plugins:structs:1.20@jar'
    testImplementation 'org.jenkins-ci.plugins:script-security:1.54@jar'
    testImplementation "org.jenkins-ci.main:jenkins-core:${jenkinsVersion}"
    testCompile "org.jenkins-ci.plugins:job-dsl:${jobDslVersion}"
    testCompile "org.jenkins-ci.plugins:job-dsl:${jobDslVersion}@jar"
    testCompile 'org.jenkins-ci.plugins:structs:1.20@jar'
    testCompile 'org.jenkins-ci.plugins:script-security:1.54@jar'

    // Plugins to install in test instance
    testPlugins 'org.jenkins-ci.plugins:cloudbees-folder:5.14'
    testPlugins 'org.jenkins-ci.plugins:credentials:2.1.10'
    testPlugins 'org.jenkins-ci.plugins:cvs:2.13'
    testPlugins 'org.jenkins-ci.plugins:ghprb:1.40.0'
    testPlugins 'org.jenkins-ci.plugins:token-macro:2.5'
    testPlugins 'org.jenkins-ci.plugins.workflow:workflow-cps-global-lib:2.7'


}

task resolveTestPlugins(type: Copy) {
    from configurations.testPlugins
    into new File(sourceSets.test.output.resourcesDir, 'test-dependencies')
    include '*.hpi'
    include '*.jpi'
    def mapping = [:]

    doFirst {
        configurations.testPlugins.resolvedConfiguration.resolvedArtifacts.each {
            mapping[it.file.name] = "${it.name}.${it.extension}"
        }
    }
    rename { mapping[it] }

    doLast {
        List<String> baseNames = source*.name.collect { mapping[it] }.collect { it[0..it.lastIndexOf('.') - 1] }
        new File(destinationDir, 'index').setText(baseNames.join('\n'), 'UTF-8')
    }
}

test {
    dependsOn tasks.resolveTestPlugins
    inputs.files sourceSets.jobs.groovy.srcDirs
    useJUnitPlatform()
}
```

```
# gradle.properties
jobDslVersion=1.77
jenkinsVersion= 2.445
// JobRulesSpec.groovy
package com.example

import groovy.util.FileNameFinder
import javaposse.jobdsl.dsl.DslScriptLoader
import javaposse.jobdsl.plugin.JenkinsJobManagement
import org.junit.ClassRule
import org.jvnet.hudson.test.JenkinsRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll

class JobRulesSpec extends Specification {
    @Shared
    @ClassRule
    private JenkinsRule jenkinsRule = new JenkinsRule()

    @Unroll
    def 'test script #file.name'() { //File file
        given:
        println "==============> ${jenkinsRule.instance}"
        def jobManagement = new JenkinsJobManagement(System.out, [:], new File('.'))

        when:
        new DslScriptLoader(jobManagement).runScript(file.text)

        then:
        noExceptionThrown()

        where:
        file << new FileNameFinder().getFileNames('src/jobs', '**/*.groovy').collect { new File(it) }
    }
}
result 

<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="net.regnology.JobRulesSpec" tests="1" skipped="0" failures="1" errors="0" timestamp="2024-02-19T16:49:41" hostname="INV-PC-432" time="0.683">
  <properties/>
  <testcase name="test script job.groovy" classname="net.regnology.JobRulesSpec" time="0.683">
    <failure message="Expected no exception to be thrown, but got 'java.lang.IllegalStateException'" type="org.spockframework.runtime.UnallowedExceptionThrownError">Expected no exception to be thrown, but got 'java.lang.IllegalStateException'
	at app//spock.lang.Specification.noExceptionThrown(Specification.java:118)
	at net.regnology.JobRulesSpec.test script #file.name(jobsRulesSpec.groovy:27)
Caused by: java.lang.IllegalStateException: Jenkins.instance is missing. Read the documentation of Jenkins.getInstanceOrNull to see what you are doing wrong.
	at jenkins.model.Jenkins.get(Jenkins.java:819)
	at javaposse.jobdsl.plugin.LookupStrategy.lambda$static$0(LookupStrategy.java:19)
	at javaposse.jobdsl.plugin.LookupStrategy.getContext(LookupStrategy.java:57)
	at javaposse.jobdsl.plugin.LookupStrategy.getItem(LookupStrategy.java:42)
	at javaposse.jobdsl.plugin.JenkinsJobManagement.createOrUpdateConfig(JenkinsJobManagement.java:136)
	at javaposse.jobdsl.dsl.AbstractDslScriptLoader.extractGeneratedJobs_closure4(AbstractDslScriptLoader.groovy:204)
	at groovy.lang.Closure.call(Closure.java:412)
	at groovy.lang.Closure.call(Closure.java:428)
	at javaposse.jobdsl.dsl.AbstractDslScriptLoader.extractGeneratedJobs(AbstractDslScriptLoader.groovy:197)
	at javaposse.jobdsl.dsl.AbstractDslScriptLoader.extractGeneratedItems(AbstractDslScriptLoader.groovy:184)
	at javaposse.jobdsl.dsl.AbstractDslScriptLoader.runScripts_closure1(AbstractDslScriptLoader.groovy:63)
	at groovy.lang.Closure.call(Closure.java:412)
	at groovy.lang.Closure.call(Closure.java:428)
	at javaposse.jobdsl.dsl.AbstractDslScriptLoader.runScripts(AbstractDslScriptLoader.groovy:46)
	at javaposse.jobdsl.dsl.AbstractDslScriptLoader.runScript(AbstractDslScriptLoader.groovy:87)
	at net.regnology.JobRulesSpec.test script #file.name(jobsRulesSpec.groovy:24)
</failure>
  </testcase>
  <system-out><![CDATA[==============> null
Processing provided DSL script
]]></system-out>
  <system-err><![CDATA[Feb 19, 2024 5:49:41 PM javaposse.jobdsl.plugin.JenkinsJobManagement createOrUpdateConfig
INFO: createOrUpdateConfig for test
]]></system-err>
</testsuite>

i take same question on jenkins test harness repository but from authors of jenkins test harness seems not possible : i can initialize spock test · Issue #732 · jenkinsci/jenkins-test-harness · GitHub

if you have any idea that’s great thx

regards

I belive that you need add spock-junit4 or add junit5 see this Release Notes
At least this fix my issue which I hit few days ago when updating my script.

thx decide to use directly junit instead of spock . seems pretty fine now