JenkinsPipelineUnit icon indicating copy to clipboard operation
JenkinsPipelineUnit copied to clipboard

Handling the Jenkins or Hudson property

Open citizenkahn opened this issue 5 years ago • 8 comments

When pipeline groovy Hudson and Jenkins are defined as global variables. I use this endpoint to access build information (last successful build etc).

What's the recommend approach to making these avail in the unit tests?

Is it to mock out jenkins.model.Jenkins and then attach it to the class under test's metaClass to simulate the provided property or is there a better approach?

import static org.mockito.Mockito.*

...
    void test() {
        Jenkins jenkinsMock = mock(Jenkins.class)
        myClass = new MyClass(env, thisScript)
        myClass.metaClass.Jenkins = jenkinsMock
        myClass.doThing()
    }

citizenkahn avatar Apr 18 '19 21:04 citizenkahn

Here's a possible work around

  1. Isolate the jenkins instance into a class
  2. mock Jenkins instance in that class
class BuildServerGlobals {
    static def getInstance() {
        return Jenkins.getInstanceOrNull()
    }
}

class myClass {
  def getJob() { buildServerGlobals.instance.getItemByFullName(jobName) }

Test

@RunWith(PowerMockRunner.class)
@PrepareForTest([Jenkins.class, Job.class, Run.class, UserIdCause.class, BuildServerGlobals.class])
class MyTest extends BasePipelineTest {
    @Mock
    private Jenkins jenkins

    @Before
    void setUp() {
        PowerMockito.mockStatic(Jenkins.class)
        PowerMockito.when(Jenkins.getInstance()).thenReturn(jenkins)
        PowerMockito.mockStatic(BuildServerGlobals.class)
        PowerMockito.when(BuildServerGlobals.getInstance()).thenReturn(jenkins)
   }

When the test runs, it uses the mock of BuildServerGlobals which returns the mocked jenkins.

citizenkahn avatar Apr 19 '19 13:04 citizenkahn

Following suggestion by @citizenkahn

@RunWith(PowerMockRunner.class)
@PrepareForTest([MyClass.class])
class CustomBuildTest extends BaseRegressionTest {
...
    @Test
    void testBasic() {
    }
}

and I get a failure with this stacktrace when running the test:

CustomBuildTest > initializationError FAILED
    java.lang.VerifyError: Stack map does not match the one at exception handler 76
    Exception Details:
      Location:
        com/lesfurets/jenkins/unit/BasePipelineTest.<init>()V @76: astore
      Reason:
        Type uninitializedThis (current frame, locals[0]) is not assignable to 'com/lesfurets/jenkins/unit/BasePipelineTest' (stack map, locals[0])
      Current Frame:
        bci: @66
        flags: { flagThisUninit }
        locals: { uninitializedThis, '[Lorg/codehaus/groovy/runtime/callsite/CallSite;', uninitializedThis, 'com/lesfurets/jenkins/unit/PipelineTestHelper', top, 'java/lang/Object' }
        stack: { 'java/lang/Throwable' }
      Stackmap Frame:
        bci: @76
        flags: { }
        locals: { 'com/lesfurets/jenkins/unit/BasePipelineTest', '[Lorg/codehaus/groovy/runtime/callsite/CallSite;', 'com/lesfurets/jenkins/unit/BasePipelineTest', 'com/lesfurets/jenkins/unit/PipelineTestHelper', top, 'java/lang/Object' }
        stack: { 'java/lang/Throwable' }
      Bytecode:
        0000000: b800 2f4c 2a2b 12c8 3212 13b9 0056 0200
        0000010: 1213 b800 5ac0 0013 4e4d 1300 cab8 00cd
        0000020: 04bd 0004 5903 2d53 1300 ceb8 007a b800
        0000030: d13a 0519 05b2 0084 a500 0a2a b700 2ba7
        0000040: 000a 2c2d b700 d301 57a7 000f 3a08 013a
        0000050: 072a b800 c719 08bf 013a 072a b800 c7b1
        0000060:                                        
      Exception Handler Table:
        bci [63, 76] => handler: 76
      Stackmap Table:
        full_frame(@66,{UninitializedThis,Object[#21],UninitializedThis,Object[#19],Top,Object[#4]},{})
        full_frame(@73,{Object[#2],Object[#21],Object[#2],Object[#19],Top,Object[#4]},{})
        same_locals_1_stack_item_frame(@76,Object[#37])
        full_frame(@88,{Object[#2],Object[#21],Object[#2],Object[#19],Top,Object[#4]},{})
Results: FAILURE (1 tests, 0 successes, 1 failures, 0 skipped)

I'm really new to this testing framework and to Groovy itself and I don't know how to fix this issue, any help would be appreciated.

Thanks!


Gradle definition:

    testCompile 'org.assertj:assertj-core:3.15+'
    testCompile "com.lesfurets:jenkins-pipeline-unit:1.5"
    testCompile 'org.powermock:powermock-module-junit4:2.0.7'
    testCompile 'org.powermock:powermock-api-mockito2:2.0.7'
⇒  java --version
openjdk 11.0.5 2019-10-15
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.5+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.5+10, mixed mode)

jgsogo avatar May 21 '20 16:05 jgsogo

@jgsogo that seems like a different situation. Without more details on the classes etc it's difficult to see what the problem might be. Perhaps hunting for Stack map does not match the one at exception handler will turn up someone with a similar issue

citizenkahn avatar May 21 '20 16:05 citizenkahn

I'm working on a Jenkins shared library. There is a src folder and a vars one. The class MyClass belongs to the src directory where there are tons of classes and functionality that I'll need to mock eventually.

I'm trying to add some testing to my Jenkinsfiles, so I need to mock functionality inside the src part because they calling to external services and so on.

I use the following in my setUp to load the library from the sources in the same repository:

@RunWith(PowerMockRunner.class)
@PrepareForTest([C3iArtifactory.class])
class CustomBuildTest extends BaseRegressionTest {

    ...

    @Before
    void setUp() {
        callStackPath = "test/test_jobs/callstacks/"
        super.setUp()
        def library = library().name('my-shared-lib')
                .defaultVersion('<notNeeded>')
                .allowOverride(true)
                .implicit(false)
                .targetPath('<notNeeded>')
                .retriever(projectSource())
                .build()
        helper.registerSharedLibrary(library)
    }

     @Override
    void registerAllowedMethods() {
        super.registerAllowedMethods()
        ...
    }

    @Test
    void testBasic() {
        //MyClass mock = PowerMockito.mock(MyClass.class)
        //PowerMockito.when(mock.doAction()).thenReturn(null);

        def script = runScript("jobs/custom_build.jenkins")
        printCallStack()
        assertJobStatusSuccess()
        //super.testNonRegression('basic')
    }
}

I'm not sure if it is possible at all to mock classes and functions from the src directory when you are testing the pipeline this way: Is the src already compiled and maybe a different library, in a different workspace/runtime when running this kind of tests?


The workaround I can think of is to move the functionality I want to mock to some vars/xxxx.groovy and then mock them using https://github.com/jenkinsci/JenkinsPipelineUnit/issues/138#issuecomment-631398630. But it will require to rewrite many classes and functions inside the src to have access to the steps/script so I can call functions in those vars/xxxxx.groovy files 😞


If you know where I can have a look at a working project with this kind of testing (mocking classes/functions inside the src directory), maybe I can figure out what I'm doing differently in my project.

Thanks a lot

jgsogo avatar May 21 '20 16:05 jgsogo

FYI, If I use <2.0.0:

    testCompile 'org.powermock:powermock-module-junit4:1.7.4'
    testCompile 'org.powermock:powermock-api-mockito2:1.7.4'

then I get a different error

test_jobs.CustomBuildTest > initializationError FAILED
    org.objenesis.ObjenesisException: java.lang.reflect.InvocationTargetException
        Caused by:
        java.lang.reflect.InvocationTargetException
            Caused by:
            java.lang.IllegalAccessError: class jdk.internal.reflect.ConstructorAccessorImpl loaded by org.powermock.core.classloader.MockClassLoader @6a19313b cannot access jdk/internal/reflect superclass jdk.internal.reflect.MagicAccessorImpl
Results: FAILURE (1 tests, 0 successes, 1 failures, 0 skipped)

jgsogo avatar May 21 '20 16:05 jgsogo

FYI, If I use <2.0.0:

    testCompile 'org.powermock:powermock-module-junit4:1.7.4'
    testCompile 'org.powermock:powermock-api-mockito2:1.7.4'

then I get a different error

test_jobs.CustomBuildTest > initializationError FAILED
    org.objenesis.ObjenesisException: java.lang.reflect.InvocationTargetException
        Caused by:
        java.lang.reflect.InvocationTargetException
            Caused by:
            java.lang.IllegalAccessError: class jdk.internal.reflect.ConstructorAccessorImpl loaded by org.powermock.core.classloader.MockClassLoader @6a19313b cannot access jdk/internal/reflect superclass jdk.internal.reflect.MagicAccessorImpl
Results: FAILURE (1 tests, 0 successes, 1 failures, 0 skipped)

Did you solved the problem? I have the same issues like you when I try to use the powermock with JeninsPipelineUnit

Yihan1993 avatar Jun 17 '21 13:06 Yihan1993

I having the same problem while using @PrepareForTest I'm trying to mock "Jenkins.getInstance()" and having exactly the same error. I found that this happens since version .1.6 of this library. If you try with version 1.5 it works, but you lose the DockerMock implementation. I thought it was a matter of dependencies, but stlll, I havent found the root cause. Any ideas?

roDew avatar Oct 28 '21 16:10 roDew

I've found something similar here

For my use cases it was implemented in test base class like that:

@Shared
def jenkinsMockInstance = Mockito.mock(Jenkins.class)

void setupSpec() {
        Jenkins.HOLDER = new JenkinsHolder() {
            Jenkins getInstance() {
                return jenkinsMockInstance
            }
        }
    }

And in test cases I can specify additional behaviour for jenkinsMockInstance e.g.

Mockito.when(jenkinsMockInstance.queue).thenReturn(Mockito.mock(Queue))

sams-gleb avatar Nov 23 '21 16:11 sams-gleb