0

I have Groovy PSL project and I would like to add tests for code that calls outside classes. So, for example, I would like to mock this DeploymentMetrics class and it's methods so it doesn't call real class. All I want is to verify it was called.

CICDTest.groovy

import com.lesfurets.jenkins.unit.BaseRegressionTest
import org.junit.Before
import org.junit.Test

class CICDTest extends BaseRegressionTest {
    def serviceVersion = "12345rds234"
    String team = "TEAM"
    String service = "SERVICE"
    String deployEnv = "Development"
    String deployType = "App Code Deployment"
    String deployReason = "Routing Change"

    @Override
    @Before
    void setUp() throws Exception {
        super.setUp()
        callStackPath = "test/vars/callstacks/"
        binding.setProperty('steps', 'some')
        binding.setVariable('env', [getEnvironment: {[:]}])
    }

    @Test
    void publishCdMetricsSuccess() throws Exception {
        cicd = loadScript('vars/cicd.groovy')
        cicd.publishCdMetrics(serviceVersion, team, service, deployEnv, deployType, deployReason)
        printCallStack()
    }
}

/vars/cicd.groovy

import spg.dora.cd.DeploymentMetrics
def publishCdMetrics(serviceVersion, String team, String service, String deployEnv, String deployType, String deployReason) {
    try {
        String[] deployRegions = ['us-west-2'] as String[]
        DeploymentMetrics metricsHandler = new DeploymentMetrics(steps, env, docker)
        metricsHandler.postMetric(team, service, serviceVersion, deployEnv, deployType, deployReason, deployRegions)
    } catch (Exception exception) {
        error "Failed to publish CD metrics: ${exception}"
    }
}

DeploymentMetrics.groovy <- not owned by me (cannot modify)

class DeploymentMetrics implements Serializable {
    DeploymentMetrics(steps, env, docker) {
        this.steps = steps
        this.env = env
        this.common_shell = new CommonShell(steps, env)
    }

    def postMetric(String team, String service, String serviceVersion, String environment, String category, String reason, String[] awsRegions) {
      ...
      //doesn't return anything
    }
}

I tried new MockFor() following this example:

def mockDeploymentMetrics = new MockFor(DeploymentMetrics, true)
mockDeploymentMetrics.demand.with {
     DeploymentMetrics(any(), any(), any()) {
         new DeploymentMetrics(null, null, null)
     }
     postMetric { }
}
mockDeploymentMetrics.use {
     cicd = loadScript('vars/cicd.groovy')
     cicd.publishCdMetrics(serviceVersion, team, service, deployEnv, deployType, deployReason)
}

I tried different combinations of Mock and then PowerMock

@PrepareForTest(DeploymentMetrics.class)
class CICDTest extends BaseRegressionTest {
    @Override
    @Before
    void setUp() throws Exception {
       super.setUp()
       //deploymentMetrics = mock(DeploymentMetrics.class)
       deploymentMetrics = PowerMockito.mock(DeploymentMetrics.class)
       when(deploymentMetrics.postMetric(any(), any(), any(), any(), any(),any(), any())).thenReturn(new Object())
   }
}

I tried metaClass.constructor

DeploymentMetrics.metaClass.constructor = { steps, env, docker ->
   def constructor = DeploymentMetrics.class.getConstructor(Object.class,Object.class,Object.class)
   def instance = constructor.newInstance(any(), any(), any())

I tried instantiating class and spying on that object following this example:

DeploymentMetrics deploymentMetrics = new DeploymentMetrics(binding.getVariable("steps"), binding.getVariable("env"), binding.getVariable("docker"))
deploymentMetricsSpy = spy(deploymentMetrics)
doReturn(new Object()).when(deploymentMetricsMock).postMetric(anyString(), anyString(), anyString(), anyString(), anyString(), anyString(), any())

But it is always going inside real class and it's method. I actually do not want it to ever go inside real constructor, cause it calls other classes' constructors and it fails. So I want to avoid doing new on it completely.

Note: DeploymentMetrics only has 1 constructor that takes 3 Object parameters

I followed many examples out there, but nothing worked.

Can someone shed some light?

Angelina
  • 2,175
  • 10
  • 42
  • 82

1 Answers1

0

Regarding the PowerMock version: to mock a constructor, you should PrepareForTests the class in which the constructor is called, not the class being constructed. You need to use the PowerMockRunner as well. Try changing:

@PrepareForTest(DeploymentMetrics.class)
class CICDTest extends BaseRegressionTest {
...
}

to

@RunWith(PowerMockRunner.class)
@PrepareForTest(cicd.class)
class CICDTest extends BaseRegressionTest {

    @Override
    @Before
    void setUp() throws Exception {
       deploymentMetrics = mock(DeploymentMetrics.class)
       PowerMockito.whenNew(DeploymentMetrics.class).withAnyArguments().thenReturn(deploymentMetrics)
       when(deploymentMetrics.postMetric(any(), any(), any(), any(), any(),any(), any())).thenReturn(new Object())
   }
}
Mafor
  • 9,668
  • 2
  • 21
  • 36
  • it doesn't like `@PrepareForTest(cicd.class)` Perhaps because it is in /vars folder? ANyways, `PowerMockito.mock(DeploymentMetrics.class)` does give me a mock, I can see it when I debug, but it still calls real class. – Angelina Jan 29 '21 at 00:07
  • Actually you need to mock the constructor as well, using `PowerMockito.whenNew`, I updated my answer. Regarding `@PrepareForTest(cicd.class)`: it won't work without it. Try checking whats the actual name of the class the `cicd.groovy` is compiled to. – Mafor Jan 29 '21 at 10:18