Somewhere in my shared library I got a helper class like this:
class Helper {
def script
Helper(script) {
this.script = script
}
void sendTemplate(String webhook, String template, Map<String, String> values, TemplateMapper mapper) {
def body = mapper.map(template, values)
def resp = script.httpRequest(contentType: 'APPLICATION_JSON', httpMode: 'POST',
requestBody: body, url: webhook)
if (resp.status != 200) {
throw new UnableToNotifyException()
}
}
}
I'm trying to test said class like so:
class HelperSpec extends JenkinsPipelineSpecification {
def helper
def setup() {
helper = new Helper(this)
}
def "a test"() {
setup:
def webhook = 'aWebhook'
def template = '%replaceMe'
def values = ['%replaceMe': 'hello world!']
def mapper = new SimpleTemplateMapper()
getPipelineMock('httpRequest')(_) >> [status: 200]
when:
helper.sendTemplate(webhook, template, values, mapper)
then:
1 * getPipelineMock('httpRequest')(_)
}
}
I'm using gradle and my build.gradle file has
testImplementation 'org.jenkins-ci.plugins:http_request:1.10#jar'
Other steps' tests run perfectly but with this one I always get
java.lang.IllegalStateException: There is no pipeline step mock for [httpRequest].
1. Is the name correct?
2. Does the pipeline step have a descriptor with that name?
3. Does that step come from a plugin? If so, is that plugin listed as a dependency in your pom.xml?
4. If not, you may need to call explicitlyMockPipelineStep('httpRequest') in your test's setup: block.
And when I use explicitlyMockPipelineStep('httpRequest') I get a null pointer exception, because, I presume, the default mock returns a null.
Is there anything I'm missing in the test to get it working? Thanks in advance!!!
Related
This is specific to Jenkins but hoping there is a generic groovy feature that can help me here.
I have a groovy script (myCustomStep.grooy) I want to unit test. It MUST be written like it is below (it cannot be a class). It will include methods that are available during Jenkins run time but not locally and I want to mock them out.
Here is one of these scripts and a corresponding test. How do I mock out echo without modifying myCustomStep.groovy?
# vars/myCustomStep.grooy
def call(Map config) {
def paramOne = config.paramOne
echo paramOne
}
class MyCustomStepTest {
// I tried to define it here but I get "No signature of method: myCustomStep.echo()"
def echo(message) {
println "$message"
}
#Test
public void "sdfsdfsdf"() throws Exception {
def aaa = new GroovyShell().parse( new File( 'vars/myCustomStep.groovy' ) )
aaa deployment: "sdlfsdfdsf"
}
}
I can't have myCustomStep.grooy accept echo as an argument. Is there a way to monkey patch echo into the myCustomStep namespace?
EDIT: I found a simple solution but now I want to know how I can attach methods to myCustomStep for all tests instead of having to redefine for every test. I tried to do this in a #Before method (using junit) but the myCustomStep obj wasn't available to the tests.
class MyCustomStepTest {
def myCustomStep = new GroovyShell().parse( new File( 'vars/myCustomStep.groovy' ) )
#Test
public void "sdfsdfsdf"() throws Exception {
// how can I attach this once for use by all my tests?
myCustomStep.echo = { String message -> println "$message" }
myCustomStep deployment: "sdlfsdfdsf"
}
}
EDIT:
I was just confused about where to instantiate the object. Looks like I just need to create the object outside of the #before method and then update it inside of it.
#Before
public void setUp() throws Exception {
myCustomStep.echo = { String message -> println "$message" }
}
def myCustomStep = new GroovyShell().parse( new File( 'vars/myCustomStep.groovy' ) )
You could put echo in the binding using something like this:
Binding b = new Binding()
b.echo = { println "Hello There" }
def shell = new GroovyShell(b)
def aaa = shell.parse( new File( 'ars/myCustomStep.groovy' ) )
aaa deployment: "sdlfsdfdsf"
I'm trying to create a JobGenerator class that will pass a build step down to the calling instance. I'm running into an issue where if I get this error when I try to run this:
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: org.jenkinsci.plugins.workflow.cps.CpsClosure2.build() is applicable for argument types: (java.util.LinkedHashMap) values: [[job:FooJob]]
class BuildGenerator implements Serializable {
static def generateJob() {
return [
"TestJob",
{ ->
build(
job: 'FooJob'
)
},
]
}
}
node(){
def tasks = [:]
def label
def task
stage("Build") {
def generator = new BuildGenerator()
tasks["Testing"] = generator.generateJob()[1]
parallel tasks
}
}
If I remove the generateJob function outside of the class then it works fine. What am I doing wrong with closures here? I'm new to groovy/jenkins world.
Well... This is the way Groovy/Jenkins pipeline work. build is available inside node as the rest of steps and functions. If you wish to access these you have to pass the CPS instance to the method, like this (or use constructor to pass the instance only once):
class BuildGenerator implements Serializable {
static def generateJob(script) {
return [
"TestJob",
{ ->
script.build(
job: 'FooJob'
)
},
]
}
}
node(){
def tasks = [:]
def label
def task
stage("Build") {
def generator = new BuildGenerator()
tasks["Testing"] = generator.generateJob(this)[1]
parallel tasks
}
}
Service:
#GrailsCompileStatic
class MyService {
final static String PLACEHOLDER = '<x>'
#Value('${myService.url}') // Suppose it http://example.com/doc-<x>.html
private String urlTemplate
String getSomeUrl(String lang) {
urlTemplate.replace(PLACEHOLDER, lang)
}
}
Unit test:
#TestFor(MyService)
class MyServiceSpec extends Specification {
#Unroll
void "test get some url for #lang"() {
when:
def someUrl = service.getSomeUrl(lang) // NullPointerException, because urlTemplate is null
then:
someUrl.endsWith(lang + '.html')
where:
lang << ['es', 'en']
}
}
So, as I mentioned above, urlTemplate is null (but config value exists in .properties). How to fix it?
Solution:
class MyServiceSpec extends IntegrationSpec {
MyService myService
#Unroll
void "test get some url for #lang"() {
when:
def someUrl = myService.getSomeUrl(lang)
then:
someUrl.endsWith(lang + '.html')
where:
lang << ['es', 'en']
}
}
Unit tests are used to test isolated units of code. If you are testing behavior that is dependent on the configuration value, inject it into the unit test to achieve reusable unit tests.
On the other hand, if you are testing that the variable is actually set or what the variable is set to, you need to use an integration test because you are basically testing your integration with Grails' configuration mechanism: http://docs.grails.org/latest/guide/testing.html#integrationTesting
As a third option, you could also use functional testing to verify that in the end everything appears to function as it is supposed to: http://docs.grails.org/latest/guide/testing.html#functionalTesting
How to bind #Value annotated fields of service in unit tests?
One way to do it...
#TestFor(MyService)
class MyServiceSpec extends Specification {
#Unroll
void "test get some url for #lang"() {
when:
service.urlTemplate = 'some value'
def someUrl = service.getSomeUrl(lang)
then:
someUrl.endsWith(lang + '.html')
where:
lang << ['es', 'en']
}
}
In case this helps someone.
I had an issue where a missing property variable used in a #Value was giving me an BeanExpressionException for a service unit test. I found that setting that variable in my application.yml for the test environment solved my problem in Grails 4.
#Value in question:
#Value("#{\${some_property_here}}") public Map<String, Map<String, Integer>> SOME_MAP_OF_MAPS
Within a Spock unit test, I am trying to test the behaviour of a method findRepositoriesByUsername independent of getGithubUrlForPath, both belonging to the same service.
Repeated attempts to use the metaClass have failed:
String.metaClass.blarg produces an error No such property: blarg for class: java.lang.String
service.metaClass.getGithubUrlForPath to modify the service instance doesn't work
GithubService.metaClass.getGithubUrlForPath to modify the service class doesn't work
Tried adding/modifying methods on the metaClass in the test methods' setup and when blocks, neither worked as expected
The test:
package grails.woot
import grails.test.mixin.TestFor
#TestFor(GithubService)
class GithubServiceSpec extends spock.lang.Specification {
def 'metaClass test'() {
when:
String.metaClass.blarg = { ->
'brainf***'
}
then:
'some string'.blarg == 'brainf***'
}
def 'can find repositories for the given username'() {
given:
def username = 'username'
def requestPathParts
when: 'the service is called to retrieve JSON'
service.metaClass.getGithubUrlForPath = { pathParts ->
requestPathParts = pathParts
}
service.findRepositoriesByUsername(username)
then: 'the correct path parts are used'
requestPathParts == ['users', username, 'repos']
}
}
The service:
package grails.woot
import grails.converters.JSON
class GithubService {
def apiHost = 'https://api.github.com/'
def findRepositoriesByUsername(username) {
try{
JSON.parse(getGithubUrlForPath('users', username, 'repos').text)
} catch (FileNotFoundException ex) {
// user not found
}
}
def getGithubUrlForPath(String ... pathParts) {
"${apiHost}${pathParts.join('/')}".toURL()
}
}
I've tested the String.metaClass.blarg example in the groovy shell (launched by grails), and it did as expected.
Do I have a fundamental misunderstanding here? What am I doing wrong? Is there a better way to handle the desired test (replacing a method on the service under test)?
This is how the tests can be written to make them pass:
def 'metaClass test'() {
given:
String.metaClass.blarg = { -> 'brainf***' }
expect:
// note blarg is a method on String metaClass
// not a field, invoke the method
'some string'.blarg() == 'brainf***'
}
def 'can find repositories for the given username'() {
given:
def username = 'username'
def requestPathParts
when: 'the service is called to retrieve JSON'
service.metaClass.getGithubUrlForPath = { String... pathParts ->
requestPathParts = pathParts
[text: 'blah'] // mimicing URL class
}
service.findRepositoriesByUsername(username)
then: 'the correct path parts are used'
requestPathParts == ['users', username, 'repos']
}
Why don't you use Spock's great Mocking abilities?
Look at http://spockframework.github.io/spock/docs/1.0/interaction_based_testing.html#_creating_mock_objects
There is no need to peek inside metaclass itself, you can create some stub object, and demanded method will be called instead of original one. Also you can use Groovy's MockFor and StubFor, they can be a little bit easier.
You cannot fully trust metaclass inside spock tests specification.
There is some complex logic inside it, which can easyly mess thing's up. Try run some tests under debugger, and you will see it.
Spock uses metaclasses under the hood. It can override your own try's.
I have one service in that i have a method to call and how can i acces this service.
I have seen sms plugin and installed it and How can i send sms from my application to the different mobiles.I followed the grails sms plugin but i didn't get any results ecxept ecxeptions
class SipgateService {
static transactional = true
def serviceMethod() {
def sipgateService
//def phoneNumber = 'XXXXXXXXXX' //phoneNumber according to E.164 specification
//working alternative:
println "service"
def phoneNumber = 'XXXXXXXXXX'
def result = sipgateService.sendSMS(phoneNumber, 'This is my Text to send!')
result ? 'Sending Successful':'Sending failed'
println "after service"
}
}
Please explain me with an example.
thanks alot in advance.
If you want to call the plugin from a service method, you would need to do:
change the name of your service (so it isn't called SipgateService)
Add the def sipgateService as a class definition, not a method one
Does this work?
class MySMSService {
static transactional = true
def sipgateService // This will be injected from the SMS plugin
def serviceMethod() {
println "service"
def phoneNumber = 'XXXXXXXXXX'
def result = sipgateService.sendSMS(phoneNumber, 'This is my Text to send!')
result ? 'Sending Successful':'Sending failed'
println "after service"
}
}
Then, from a controller, define the link to MySMSService at class level, and call your serviceMethod method ie:
class MyController {
def mySMSService // this will be injected from your service
// then, when you want to use it (from an action)
def someAction = {
...
mySMSService.serviceMethod()
...
}
}