Environment : Rails 3.1.1 and Rspec 2.10.1
I am loading all my application configuration through an external YAML file. My initializer (config/initializers/load_config.rb) looks like this
AppConfig = YAML.load_file("#{RAILS_ROOT}/config/config.yml")[RAILS_ENV]
And my YAML file sits under config/config.yml
development:
client_system: SWN
b2c_agent_number: '10500'
advocacy_agent_number: 16202
motorcycle_agent_number: '10400'
tso_agent_number: '39160'
feesecure_eligible_months_for_monthly_payments: 1..12
test:
client_system: SWN
b2c_agent_number: '10500'
advocacy_agent_number: 16202
motorcycle_agent_number: '10400'
tso_agent_number: '39160'
feesecure_eligible_months_for_monthly_payments: 1..11
And I access these values as, For example AppConfig['feesecure_eligible_months_for_monthly_payments']
In one of my tests I need AppConfig['feesecure_eligible_months_for_monthly_payments'] to return a different value but am not sure how to accomplish this. I tried the following approach with no luck
describe 'monthly_option_available?' do
before :each do
#policy = FeeSecure::Policy.new
#settlement_breakdown = SettlementBreakdown.new
#policy.stub(:settlement_breakdown).and_return(#settlement_breakdown)
#date = Date.today
Date.should_receive(:today).and_return(#date)
#config = mock(AppConfig)
AppConfig.stub(:feesecure_eligible_months_for_monthly_payments).and_return('1..7')
end
.....
end
In my respective class I am doing something like this
class Policy
def eligible_month?
eval(AppConfig['feesecure_eligible_months_for_monthly_payments']).include?(Date.today.month)
end
....
end
Can someone please point me in the right direction!!
The method that is being called when you do AppConfig['foo'] is the [] method, which takes one argument (the key to retrieve)
Therefore what you could do in your test is
AppConfig.stub(:[]).and_return('1..11')
You can use with to setup different expectations based on the value of the argument, ie
AppConfig.stub(:[]).with('foo').and_return('1..11')
AppConfig.stub(:[]).with('bar').and_return(3)
You don't need to setup a mock AppConfig object - you can stick your stub straight on the 'real' one.
Related
I have a groovy file created under "vars" in a Jenkins-shared-lib.
Few variables are defined inside call().
I want to mock the variables in a groovy test file.
Variables are defined in sonarGradleProject.groovy in vars:
#!/usr/bin/env groovy
import static com.example.jenkins.Constants.*
def call(Map params = [:]) {
def sonarCredId = params.sonarCredentialId ?: SONAR_CREDENTIAL_ID_DEFAULT;
def sonarUrl = params.sonarUrl ?: SONAR_URL;
def profile = params.profile ?: 'example';
withCredentials([string(credentialsId: sonarCredId, variable: 'SONAR_TOKEN')]) {
sh "./gradlew --no-daemon jacocoTestReport sonarqube -P${profile} -Dsonar.host.url=${sonarUrl} -Dsonar.login=$SONAR_TOKEN -Dsonar.verbose=true"
}
}
Test file looks like this:
import com.example.jenkins.testing.JenkinsPipelineSpecification
class SonarGradleProjectSpec extends JenkinsPipelineSpecification {
def "expected minor value"() {
setup:
def sonarGradleProject = loadPipelineScriptForTest("vars/sonarGradleProject.groovy")
explicitlyMockPipelineStep('withCredentials')
when:
def verType = sonarGradleProject(profile: 'foo')
then:
1 * getPipelineMock("sh")("./gradlew --no-daemon jacocoTestReport sonarqube -P${profile} -Dsonar.host.url=${sonarUrl} -Dsonar.login=$SONAR_TOKEN -Dsonar.verbose=true")
expect:
'foo' == profile
}
}
On executing the test case, I get this error:
java.lang.IllegalStateException: There is no pipeline variable mock for [profile].
1. Is the name correct?
2. Is it a GlobalVariable extension point? If so, does the getName() method return [profile]?
3. Is that variable normally defined by Jenkins? If so, you may need to define it by hand in your Spec.
4. Does that variable come from a plugin? If so, is that plugin listed as a dependency in your pom.xml?
5. If not, you may need to call explicitlyMockPipelineVariable("profile") during your test setup.
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at com.example.jenkins.testing.JenkinsPipelineSpecification.addPipelineMocksToObjects_closure1$_closure15(JenkinsPipelineSpecification.groovy:755)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at SonarGradleProjectSpec.expected minor value(sonarGradleProjectSpec.groovy:12)
Caused by: groovy.lang.MissingPropertyException: No such property: (intercepted on instance [SonarGradleProjectSpec#3dfb1626] during test [SonarGradleProjectSpec#3dfb1626]) profile for class: SonarGradleProjectSpec
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at com.example.jenkins.testing.JenkinsPipelineSpecification.addPipelineMocksToObjects_closure1$_closure15(JenkinsPipelineSpecification.groovy:754)
... 3 more
I want to use the variables in test.
How do I mock them?
I've run into similar issues before. The issue for me is that I was trying to verify the interactions of a mock that uses string interpolation. So for the one entry in the then section...
1 * getPipelineMock("sh")("./gradlew --no-daemon jacocoTestReport sonarqube -P${profile} -Dsonar.host.url=${sonarUrl} -Dsonar.login=$SONAR_TOKEN -Dsonar.verbose=true")
I would define the profile and sonarUrl variables in the setup section (i.e., def profile = 'test123'), referenced to verify the mocked sh step interaction. I believe you would also want to adjust the assignment of default values to use the null-safe ?. operator to access key-value pairs in the params map. So def sonarUrl = params.sonarUrl ?: SONAR_URL; would become def sonarUrl = params?.sonarUrl ?: SONAR_URL;
You may have already addressed this and omitted it from the question, but I figured I'd share this information too. Please ignore the following information if you've already addressed it. :)
The three environment variables defined in the script - SONAR_CREDENTIAL_ID_DEFAULT, SONAR_URL, and SONAR_TOKEN - will need to be injected into the script before calling sonarGradleProject. That should be sufficient to test default values if key-value pairs are not provided in the params map.
setup:
def sonarGradleProject = loadPipelineScriptForTest("vars/sonarGradleProject.groovy")
explicitlyMockPipelineStep('withCredentials')
// Inject environment variables.
// First argument is the variable name and second argument is the variable value.
sonarGradleProject.getBinding().setVariable('SONAR_CREDENTIAL_ID_DEFAULT', 'test123')
sonarGradleProject.getBinding().setVariable('SONAR_URL', 'test123')
sonarGradleProject.getBinding().setVariable('SONAR_TOKEN', 'test123')
I have this condition in my recipe:
install_action = (::Win32::Service.exists?(windows_service['name']) ? :configure : :create)
and a ChefSpec for that in spec file:
#1: not working
allow_any_instance_of(Win32::Service)
.to receive(:exists?)
.with(windows_service[:name])
.and_return(true)
#2: also not working
stub_command("::Win32::Service.exists?(#{windows_service[:name]})").and_return(true)
Could you please help to find out what have I missed in the ChefSpec test that is not working and mocking the return value.
Thanks
This should work:
allow(::Win32::Service).to receive(:exists?).with(windows_service[:name]).and_return(true)
Point is you stub a class method exists?, and not an instance method. That's why allow_any_instance_of does not work. And stub_command is actually for shell commands like stub_command('cat file | grep "hello"')
I have code that reads in a pom.xml file then attempts to re-serialize and write it back out:
// Get the file raw text
def pomXMLText = readFile(pomFile)
// Parse the pom.xml file
def project = new XmlSlurper(false, false).parseText(pomXMLText)
... do some useful stuff ...
def pomFileOut = "$WORKSPACE/pomtest.xml"
def pomXMLTextOut = groovy.xml.XmlUtil.serialize(project)
println "pomXMLTextOut = $pomXMLTextOut" // <-- This line prints to updated XML
writeFile file: pomFileOut, text: pomXMLTextOut // <-- This line crashes with the error listed in the posting title: java.io.NotSerializableException: groovy.util.slurpersupport.NodeChild
I've tried casting the pomXMLTextOut variable to a String. I tried applying the .text() method, which gets a jenkins sandbox security error. Has anyone else been able to successfully write an XML file from a groovy script running in a Jenkins pipeline?
BTW, I've also tried using a File object, but that isn't remotable across jenkins nodes. It works as long as the job always runs on master.
You could try a #NonCPS annotation and close those non-serializable objects in a funcation like this
#NonCPS
def writeToFile(String text) {
...
}
Here's the explanation from Pipeline groovy plugin
#NonCPS methods may safely use non-Serializable objects as local
variables
I'm currently trying to use ExecJS to run Handlebars for one of the product I work on (note: I know the handlebars.rb gem which is really cool and I used it for some times but there is issues to get it installed on Windows, so I try another homemade solution).
One of the problem I'm having is that the Javascript context is not kept between each "call" to ExecJS.
Here the code where I instantiate the #js attribute:
class Context
attr_reader :js, :partials, :helpers
def initialize
src = File.open(::Handlebars::Source.bundled_path, 'r').read
#js = ExecJS.compile(src)
end
end
And here's a test showing the issue:
let(:ctx) { Hiptest::Handlebars::Context.new }
it "does not keep context properly (or I'm using the tool wrong" do
ctx.js.eval('my_variable = 42')
expect(ctx.js.eval('my_variable')).to eq(42)
end
And now when I run it:
rspec spec/handlebars_spec.rb:10 1 ↵
I, [2015-02-21T16:57:30.485774 #35939] INFO -- : Not reporting to Code Climate because ENV['CODECLIMATE_REPO_TOKEN'] is not set.
Run options: include {:locations=>{"./spec/handlebars_spec.rb"=>[10]}}
F
Failures:
1) Hiptest::Handlebars Context does not keep context properly (or I'm using the tool wrong
Failure/Error: expect(ctx.js.eval('my_variable')).to eq(42)
ExecJS::ProgramError:
ReferenceError: Can't find variable: my_variable
Note: I got the same issue with "exec" instead of "eval".
That is a silly example. What I really want to do it to run "Handlebars.registerPartial" and later on "Handlebars.compile". But when trying to use the partials in the template it fails because the one registered previously is lost.
Note that I've found a workaround but I find it pretty ugly :/
def register_partial(name, content)
#partials[name] = content
end
def call(*args)
#context.js.call([
"(function (partials, helpers, tmpl, args) {",
" Object.keys(partials).forEach(function (key) {",
" Handlebars.registerPartial(key, partials[key]);",
" })",
" return Handlebars.compile(tmpl).apply(null, args);",
"})"].join("\n"), #partials, #template, args)
end
Any idea on how to fix the issue ?
Only the context you create when you call ExecJS.compile is preserved between evals. Anything you want preserved needs to be part of the initial compile.
I've been searching around for a bit and couldn't find anything that really helped me. Especially because sometimes things don't seem to be consistant.
I have the following YAML that I use to store data/ configuration stuff:
---
global:
name: Core Config
cfg_version: 0.0.1
databases:
main_database:
name: Main
path: ~/Documents/main.reevault
read_only: false
...
I know how to update fields with:
cfg = YAML.load_file("test.yml")
cfg['global']['name'] = 'New Name'
File.open("test.yml", "w"){ |f| YAML.dump(cfg, f) }
And that's essentially everybody on the internet talks about. However here is my problem: I want to dynamically be able to add new fields to that file. e.g. under the "databases" section have a "secondary_db" field with it's own name, path and read_only boolean. I would have expected to do that by just adding stuff to the hash:
cfg['global']['databases']['second_db'] = nil
cfg['global']['databases']['second_db']['name'] = "Secondary Database"
cfg['global']['databases']['second_db']['path'] = "http://someurl.remote/blob/db.reevault"
cfg['global']['databases']['second_db']['read_only'] = "true"
File.open("test.yml", "w"){ |f| YAML.dump(cfg, f) }
But I get this error:
`<main>': undefined method `[]=' for nil:NilClass (NoMethodError)
My question now is: how do I do this? Is there a way with the YAML interface? Or do I have to write stuff into the file manually? I would prefer something via the YAML module as it takes care of formatting/ indentation for me.
Hope someone can help me.
Yo have to initialize cfg['global']['database']['second_db'] to be a hash not nil. Try this cfg['global']['database']['second_db'] = {}