Testing that a plugin function is called in jenkins shared library - unit-testing

I'm trying to write a unit test for a util function in a Jenkins shared library. The util function, calls the Office365Connector plugin in Jenkins.
My util function looks like this:
#!/usr/bin/env groovy
import pe.ui.BuildNotificationSettings
import pe.ui.BuildStates
def call(BuildNotificationSettings options, BuildStates status) {
options.teamsChannelWebhookUrlList.each {
office365ConnectorSend (
status: status.toString(),
webhookUrl: "$it",
color: BuildStates.valueOf(status.toString()).color,
message: "Build ${status.toString()}: ${JOB_NAME} - ${BUILD_DISPLAY_NAME}<br>Pipeline duration: ${currentBuild.durationString}"
)
}
}
The use case that I'm trying to test is that the office365ConnectorSend function is called.
I've tried the following approach:
import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
import pe.ui.BuildNotificationSettings
import pe.ui.BuildStates
public class _sendTeamsMessageSpec extends JenkinsPipelineSpecification {
def _sendTeamsMessage = null
def setup() {
_sendTeamsMessage = loadPipelineScriptForTest("vars/_sendTeamsMessage.groovy")
def office365ConnectorSend = Mock(office365ConnectorSend)
}
def "returns without sending no build notification settings are passed" () {
given:
def options = new BuildNotificationSettings(
shouldSendNotification: null,
teamsChannelWebhookUrlList: null
)
when:
def result = _sendTeamsMessage(options, null)
then:
0 * explicitlyMockPipelineVariable("office365ConnectorSend")(_)
}
}
running this on Jenkins gives me a java.lang.IllegalStateException:
There is no pipeline variable mock for [office365ConnectorSend]., what am I doing wrong in this approach?

After following this thread, I ended up calling the explicitlyMockPipelineStep function on the office365ConnectorSend plugin function.
This made the function visible in the tests and I was able to resume testing.

Related

Unit testing a non-class Groovy function using Gradle

I was given a Jenkins Library project, and I'd like to write some unit tests for it. I'm currently using the Jenkins Pipeline Shared Libraries Gradle Plugin which sets up source paths for unit and integration tests.
The source file I want to test is located at src/org/example/pipeline/Terraform.groovy and looks like:
package org.example.pipeline
def terraformNewWorkspace(String env, Map config) {
def tfWSCreate = """
#!/bin/bash
set -e
terraform workspace new ${env} || true
terraform workspace select ${env}
terraform workspace list
"""
if (config.currentWorkingDirectory != null) {
dir("${config.currentWorkingDirectory}") {
sh(returnStatus: false, script: tfWSCreate)
}
} else {
sh(returnStatus: false, script: tfWSCreate)
}
}
and I've created a test/unit/groovy/example/PipelineTests.groovy which looks like the following:
import static org.example.pipeline.Terraform.terraformNewWorkspace as terraformNewWorkspace
class TerraformUnitTests extends GroovyTestCase {
void testNewWorkspace() {
terraformNewWorkspace("dev", [currentWorkingDirectory: "/tmp/test"])
}
}
However I get a groovy.lang.MissingMethodException at PipelineTests.groovy when I try to execute this using gradle test. I've also tried the following as some documentation seems to indicate functions that are not part of a class get added in Groovy to a class with the name of the file, but I get the same exception about a missing method.
import org.example.pipeline.Terraform
...
t = new Terraform()
t.terraformNewWorkspace("dev", [currentWorkingDirectory: "/tmp/test"])
What is the correct way to import and test this individual function using gradle/groovy?
It was a simple syntax error. I forgot to use def for my variable:
import org.example.pipeline.Terraform
class TerraformUnitTests extends GroovyTestCase {
void testNewWorkspace() {
def t = new Terraform()
t.terraformNewWorkspace("dev", [currentWorkingDirectory: "/tmp/test"])
}
}

Grails build-test-data Plugin - Can't Find TestDataConfig.groovy

I'm trying to utilize the build-test-data plugin in my Grails (v2.4.3) app to assist with test data creation for unit testing, but while running my unit tests the plugin cannot find TestDataConfig.groovy to load my specified values (for unique constraint tests, etc).
I've installed the plugin via including it in BuildConfig.groovy:
plugins {
...
test ":build-test-data:2.2.0"
...
}
I've ran the following command to create the TestDataConfig.groovy template, which places the file at \grails-app\conf\:
grails install-build-test-data-config-template
I've followed the general instructions on the plugin wiki to come up with a properly formatted file:
testDataConfig {
sampleData {
'<path>.User' {
def a = 1
username = { -> "username${a++}" }
}
}
}
(Where path is the fully-qualified class name.)
In my tests, I am using the following general format:
import grails.buildtestdata.TestDataConfigurationHolder
import grails.buildtestdata.mixin.Build
import grails.test.mixin.TestFor
import spock.lang.Specification
import spock.lang.Unroll
#TestFor(User)
#Build(User)
class UserSpec extends Specification {
def setup() {
mockForConstraintsTests(User)
TestDataConfigurationHolder.reset()
user = User.buildWithoutSave()
}
#Unroll
void "test #field must be unique"() {
given: 'a User exists'
user.save(flush: true)
when: 'another User is created with a non-unique field value'
def nonUniqueUser = User.buildWithoutSave()
nonUniqueUser."$field" = user."$field"
then: 'validation fails and the field has a unique constraint error'
!nonUniqueUser.validate()
nonUniqueUser.errors.errorCount == 1
nonUniqueUser.errors.getFieldError("$field").code == 'unique'
where:
field << ['username', '<some other field>']
}
}
But, when the test is run (using IntelliJ IDEA) TestDataConfig.groovy cannot be found via the following method in the plugin:
static Class getDefaultTestDataConfigClass() {
GroovyClassLoader classLoader = new GroovyClassLoader(TestDataConfigurationHolder.classLoader)
String testDataConfig = Holders.config?.grails?.buildtestdata?.testDataConfig ?: 'TestDataConfig'
try {
return classLoader.loadClass(testDataConfig)
} catch (ClassNotFoundException ignored) {
log.warn "${testDataConfig}.groovy not found, build-test-data plugin proceeding without config file"
return null
}
}
So the test continues on without a config file and I do not get uniquely generated data sets.
I've even tried explicitly including the file in Config.groovy:
grails.buildtestdata.testDataConfig = "TestDataConfig"
But, the same method in the plugin shows that Holders.config? is null.
I've looked at a few solutions to a similar problem here on StackOverflow with nothing working in my case; I cannot figure out how to get my app to detect the presence of the TestDataConfig.groovy file.
Any ideas? Thanks so much!

grails unit test - java.lang.NullPointerException: Cannot invoke method finish() on null object

I am not being able to run unit test in grails. I have a TransactionController(available at github as well) with method transactionAnalytics which I want to test as below,
TransactionController.groovy
package eccount
import org.codehaus.groovy.grails.web.json.JSONObject
import org.springframework.dao.DataIntegrityViolationException
import grails.converters.JSON
class TransactionController {
def transactionService
def transactionAnalytics = {
searchRequest = searchRequest ?: new SearchRequest(requestParams: new HashMap<String, String>())
configureRequestParams()
def responseBytes = transactionService.getSearchResponse(searchRequest)
def jsonResponse
if (responseBytes)
jsonResponse = JSON.parse(responseBytes)
else
jsonResponse = new JSONObject()
render jsonResponse as JSON
}
}
Corresponding Tests for TransactionController#transactionAnalytics (also available at github)is as below,
TransactionControllerTests.groovy
package eccount
import org.junit.*
import grails.test.mixin.*
#TestFor(TransactionController)
class TransactionControllerTests {
def INDEX_NAME = "gccount"
void testTransactionAnalytics(){
Map<String, String> params = new HashMap<String, String>()
params.put("indexName",INDEX_NAME)
//println params.get("indexName")
//controller.params = params
controller.params.indexName = "gccount"
controller.transactionAnalytics()
def jsonResponse = controller.response.json
println jsonResponse
}
}
When I run the method of the Controller, I get following exception
prayag#prayag:/backup/gccount$ grails test-app TransactionController.transactionAnalytics
| Environment set to test.....
| Running 1 unit test...
| Failure: Test mechanism
| java.lang.NullPointerException: Cannot invoke method finish() on null object
at org.junit.runner.notification.RunNotifier$2.notifyListener(RunNotifier.java:71)
at org.junit.runner.notification.RunNotifier$SafeNotifier.run(RunNotifier.java:41)
at org.junit.runner.notification.RunNotifier.fireTestRunFinished(RunNotifier.java:68)
at _GrailsTest_groovy$_run_closure4.doCall(_GrailsTest_groovy:290)
at _GrailsTest_groovy$_run_closure2.doCall(_GrailsTest_groovy:248)
at _GrailsTest_groovy$_run_closure1_closure21.doCall(_GrailsTest_groovy:195)
at _GrailsTest_groovy$_run_closure1.doCall(_GrailsTest_groovy:184)
at TestApp$_run_closure1.doCall(TestApp.groovy:82)
| Completed 0 unit test, 1 failed in 2723ms
| Packaging Grails application.....
[scalaPlugin] Compiling Scala sources from plugins to /home/prayag/.grails/2.1.1/projects/cashless/plugin-classes
[scalaPlugin] Compiling Scala sources to /backup/gccount/target/classes
| Compiling 2 source files
Configuring Spring Security Core ...
... finished configuring Spring Security Core
sandbox user created
role created
stall created with a user
| Tests FAILED - view reports in /backup/gccount/target/test-reports
Again, there's no reports left at file:///backup/gccount/target/test-reports.
Anyways, who is actually null here?
Resources
9 Testing - Reference Documentation
http://mrhaki.blogspot.com/2010/04/grails-goodness-invoking-single-test.html
https://stackoverflow.com/a/3736549/432903
try:
grails test-app TransactionController.testTransactionAnalytics
you forgot the "test" in front of the method name...
and yes... it seems, you don't have to write it in the classname, but in the methodname you have to...
Grails Goodness: Invoking a Single Test Method seems posting wrong information because following code wouldn't work.
$ grails test-app TransactionController.transactionAnalytics
The correct one would be with initialized transactionService
$ grails test-app TransactionControllerTests.testTransactionAnalytics
OR
$ grails test-app TransactionController.testTransactionAnalytics

How to mock a request when unit testing a service in grails

I am trying to unit test a service that has a method requiring a request object.
import org.springframework.web.context.request.RequestContextHolder as RCH
class AddressService {
def update (account, params) {
try {
def request = RCH.requestAttributes.request
// retrieve some info from the request object such as the IP ...
// Implement update logic
} catch (all) {
/* do something with the exception */
}
}
}
How do you mock the request object ?
And by the way, I am using Spock to unit test my classes.
Thank you
This code seems to work for a basic unit test (modified from Robert Fletcher's post here):
void createRequestContextHolder() {
MockHttpServletRequest request = new MockHttpServletRequest()
request.characterEncoding = 'UTF-8'
GrailsWebRequest webRequest = new GrailsWebRequest(request, new MockHttpServletResponse(), ServletContextHolder.servletContext)
request.setAttribute(GrailsApplicationAttributes.WEB_REQUEST, webRequest)
RequestContextHolder.setRequestAttributes(webRequest)
}
It can be added as a function to your standard Grails unit test since the function name does not start with "test"... or you can work the code in some other way.
One simple way to mock these, is to modify the meta class for RequestContextHolder to return a mock when getRequestAttributes() is called.
I wrote up a simple spec for doing this, and was quite surprised when it didn't work! So this turned out to be a quite interesting problem. After some investigation, I found that in this particular case, there are a couple of pitfalls to be aware of.
When you retrieve the request object, RCH.requestAttributes.request, you are doing so via an interface RequestAttributes that does not implement the getRequest() method. This is perfectly fine in groovy if the returned object actually has this property, but won't work when mocking the RequestAttributes interface in spock. So you'll need to mock an interface or a class that actually has this method.
My first attempt at solving 1., was to change the mock type to ServletRequestAttributes, which does have a getRequest() method. However, this method is final. When stubbing a mock with values for a final method, the stubbed values are simply ignored. In this case, null was returned.
Both these problems was easily overcome by creating a custom interface for this test, called MockRequestAttributes, and use this interface for the Mock in the spec.
This resulted in the following code:
import org.springframework.web.context.request.RequestContextHolder
// modified for testing
class AddressService {
def localAddress
def contentType
def update() {
def request = RequestContextHolder.requestAttributes.request
localAddress = request.localAddr
contentType = request.contentType
}
}
import org.springframework.web.context.request.RequestAttributes
import javax.servlet.http.HttpServletRequest
interface MockRequestAttributes extends RequestAttributes {
HttpServletRequest getRequest()
}
import org.springframework.web.context.request.RequestContextHolder
import spock.lang.Specification
import javax.servlet.http.HttpServletRequest
class MockRequestSpec extends Specification {
def "let's mock a request"() {
setup:
def requestAttributesMock = Mock(MockRequestAttributes)
def requestMock = Mock(HttpServletRequest)
RequestContextHolder.metaClass.'static'.getRequestAttributes = {->
requestAttributesMock
}
when:
def service = new AddressService()
def result = service.update()
then:
1 * requestAttributesMock.getRequest() >> requestMock
1 * requestMock.localAddr >> '127.0.0.1'
1 * requestMock.contentType >> 'text/plain'
service.localAddress == '127.0.0.1'
service.contentType == 'text/plain'
cleanup:
RequestContextHolder.metaClass = null
}
}

Testing a Grails controller using a mock service with GMock (0.8.0)

I have a grails 2.1 app which has a controller that calls a method on a service, passing in a request and a response:
class FooController {
def myService
def anAction() {
response.setContentType('text/xml')
myservice.service(request,response)
}
I want to unit test this method. And I want to do so using GMock (version 0.8.0), so this is what I tried:
def testAnAction() {
controller.myService = mock() {
service(request,response).returns(true)
}
play {
assertTrue controller.anAction()
}
}
Now this fails saying that that it failed expectations for request.
Missing property expectation for 'request' on 'Mock for MyService'
However, if I write my test like this:
def testAnAction() {
def mockService = mock()
mockService.service(request,response).returns(true)
controller.myService = mockService
play {
assertTrue controller.anAction()
}
}
The test will pass fine. As far as I am aware they are both valid uses of the GMock syntax, so why does the first one fail and the second one not?
Cheers,
I assume you write your tests in a test class FooControllerTest generated by grails.
In a such way, FooControllerTest class is annoted by #TestFor(FooController) wich inject some usefull attributes.
So request is an attribute of your test class, not a variable in the local scope.
It's why it is not reachable from a internal Closure.
I'm convinced that following code could work (I have not tested yet) :
def testAnAction() {
def currentRequest = request
def currentResponse = response
controller.myService = mock() {
service(currentRequest,currentResponse).returns(true)
}
play {
assertTrue controller.anAction()
}
}