Is there a way to group tests conditionally with rspec? By which I mean, is there a way to say "if this variable is some value, run this set of tests. If this variable is some other variable, run this other set of tests"?
Basic Example of where it would be needed (doesn't actually work, obviously, but should show you what I want). Assume the user to be tested is defined elsewhere and the current user being tested is #user. Although you may have better alternatives to that, that's fine.
before do
login_as_user(#user) #This logs them in and brings them to the homepage to be tested
page.visit("/homepage")
end
describe "Check the user homepage"
subject {page}
it {should have_content("Welcome, #{#user.name}!")}
if(#user.role=="admin")
it {should have_link("List Users"}
end
end
Keep in mind I have no control over the user being tested - I cannot create users on the fly, for example, and no given user is guaranteed to exist, and I don't know offhand what combination of roles a given user will have. So I do need some way to say "run this test only if these conditions are met", rather than a way to create situations where every test can be run.
You can use a let (or possibly let!) to define who the user being logged-in should be. (Obviously replace #regular_user & #admin_user with the appropriate factory/fixture/etc.)
before do
login_as_user(user)
page.visit "/homepage"
end
let(:user) { #regular_user }
describe "Check the user homepage" do
subject { page }
it { should have_content "Welcome, #{#user.name}!" }
context "when an administrator" do
let(:user) { #admin_user }
it { should have_link "List Users" }
end
end
Okay, apparently the issue was as simple as this: #user is an instance variable, which only exists when the tests are being executed. Dynamic generation of tests does not work with instance variables for that reason.
However, by declaring it as a local variable somewhere outside any test-style blocks (before, after, it or specify), you can have access to it for the conditional logic.
The solution was as simple as taking the # sign off the front of the user.
in the same kind of idea you can use static variables
In order to easily switch between environment, I even use the system global variables
in my spec_helper.rb, I've set the default settings
default_env = {
'SITE_ROOT' => "http://localhost:3000",
'ACCOUNT_ID' => 'user',
'ACCOUNT_PASSWORD' => 'password',
'SUBMIT_REAL_MONEY' => 'false'
}
# setting default env variables if no ENV ones exists
default_env.each do |const_name, value|
val = ENV[const_name].nil? ? value : ENV[const_name]
eval_str = "#{const_name}='#{val}'"
puts eval_str
eval eval_str
end
Then I can specify my settings in the command line calling my specs:
SITE_ROOT='https://my.production.site/' SUBMIT_REAL_MONEY=true rspec
And here is a spec behind a simple condition:
if SUBMIT_REAL_MONEY == 'true'
it { should respond_with 200 }
end
Related
I can't find a basic explanation anywhere about how I can test, with Rack::Test, that a Ruby/Sinatra post method successfully saves data to a YAML store/file. (This explains testing get, which I can do(!), but not post; other mentions of testing post methods with rack/test seem irrelevant.) For self-study, I'm building a "to do" app in Ruby/Sinatra and I'm trying to use TDD everything and unit test like a good little boy. A requirement I have is: When a user posts a new task, it is saved in the YML store.
I was thinking of testing this either by seeing if a "Task saved" was shown in the response to the user (which of course isn't directly testing the thing itself...but is something I'd also like to test):
assert last_response.body.include?("Task saved")
or by somehow testing that a test task's description is now in the YML file. I guess I could open up the YML file and look, and then delete it from the YML file, but I'm pretty sure that's not what I'm supposed to do.
I've confirmed post does correctly save to a YML file:
get('/') do |*user_message|
# prepare erb messages
#user_message = session[:message] if session[:message]
#overlong_description = session[:overlong_description] if
session[:overlong_description]
session[:message] = nil # clear message after being used
session[:overlong_description] = nil # ditto
#tasks = store.all
erb :index #, user_message => {:user_message => params[:user_message]}
end
post('/newtask') do
#task = Task.new(store, params)
# decide whether to save & prepare user messages
if #task.complete == true # task is complete!
#task.message << " " + "Task saved!"
session[:message] = #task.message # use session[:message] for user messages
#task.message = ""
store.save(#task)
else
#task.message << " " + "Not saved." # task incomplete
session[:message] = #task.message # use session[:message] for user messages
session[:overlong_description] = #task.overlong_description if
#task.overlong_description
#task.message = ""
#task.overlong_description = nil
end
redirect '/'
end
As you can see, it ends in a redirect...one response I want to test is actually on the slash route, not on the /newtask route.
So of course the test doesn't work:
def test_post_newtask
post('/newtask', params = {"description"=>"Test task 123"})
# Test that "saved" message for user is in returned page
assert last_response.body.include?("Task saved") # boooo
end
Github source here
If you can give me advice on a book (chapter, website, blog, etc.) that goes over this in a way accessible to a relative beginner, I'd be most grateful.
Be gentle...I'm very new to testing (and programming).
Nobody answered my question and, since I have figured out what the answer is, I thought I would share it here.
First of all, I gather that it shouldn't be necessary to check if the data is actually saved to the YAML store; the main thing is to see if the web page returns the correct result (we assume the database is groovy if so).
The test method I wrote above was correct; it was simply missing the single line follow_redirect!. Apparently I didn't realize that I needed to instruct rake/test to follow the redirect.
Part of the problem was that I simply hadn't found the right documentation. This page does give the correct syntax, but doesn't give much detail. This page helped a lot, and this bit covers redirects.
Here's the updated test method:
def test_post_newtask
post "/newtask", params = {"description" => "Write about quick brown foxes",
"categories" => "writing823"}
follow_redirect!
assert last_response.body.include?("Task saved")
assert last_response.body.include?("Write about quick brown foxes")
end
(With thanks to the Columbus Ruby Brigade.)
I've just started working with rspec, and I use expect instead of should convention.
How can I transform this test example from CanCan from should to expect?:
require "cancan/matchers"
# ...
describe "User" do
describe "abilities" do
subject { ability }
let(:ability){ Ability.new(user) }
let(:user){ nil }
context "when is an account manager" do
let(:user){ Factory(:accounts_manager) }
it{ should be_able_to(:manage, Account.new) }
end
end
end
You actually don't have to replace this instance of should, per Using implicit `subject` with `expect` in RSpec-2.11, but if you want to, you'd have to give up the one-liner approach and use:
it "should be able to manage a new account" do
expect(ability).to be_able_to(:manage, Account.new)
end
in place of the current it clause. As an aside, there looks to be some extraneous code in this test.
I have a Chef recipe for a multi-node web service, each node of which needs to get the hostname and IP of the other nodes, to put it into its own local configuration.
The code is shown below. The problem is that when the node.set[][] assignments are made in the ruby_block as shown, the values are empty when the template that relies upon them is created. If I want to create that template, I have to move all of the ruby_block code outside, and have it "loose" in the recipe. Which makes it harder to do unit-testing with Chefspec and the like.
Can any Chef guru set me straight? Is it just impossible to do node.set[] like this inside of a ruby_block? And if so, why doesn't it say so in the docs?
$cm = { :name => "web", :hostname => "" , :ip_addr => "" }
$ca = { :name => "data", :hostname => "" , :ip_addr => "" }
$cg = { :name => "gateway", :hostname => "" , :ip_addr => "" }
$component_list = [$cm, $ca, $cg]
ruby_block "get host addresses" do
block do
for cmpnt in $component_list
# do REST calls to external service to get cmpnt.hostname, ip_addr
# .......
node.set[cmpnt.name]['name'] = cmpnt.name
node.set[cmpnt.name]['host'] = cmpnt.hostname
node.set[cmpnt.name]['ip'] = cmpnt.ip_addr
end
end
end
template "/etc/app/configuration/config.xml" do
source "config.xml.erb"
variables( :dataHost => node['data']['host'],
:webHost => node['web']['host'],
:gatewayHost => node['gateway']['host'] )
action :create
end
I also added
subscribes :create, "ruby_block[get host addresses]", :immediately
to the template definition to ensure that the ruby_block ran before the template was created. This didn't make a difference.
I realize this is an old post, however for future reference, I just ran across this gist which gives a nice example of node variable assignments in the Compile vs. Converge phases. To adapt the gist to your example, you'll need to add code like the following to your ruby_block:
template_r = run_context.resource_collection.find(:template => "/etc/app/configuration/config.xml")
template_r.content node['data']['host']
template_r.content node['web']['host']
template_r.content node['gateway']['host']
For Chef 11, also see Lazy Attribute Evaluation.
The problem seems to be that attribute values inside your template resource definition get evaluated before actually invoking any resources.
I.e. the file is first executed as simple Ruby, compiling the resources, and only the the resource actions gets invoked. By that time, it is too late already.
I ran into the same problem when trying to encapsulate certain attribute manipulations into a resource. It simply does not work. Should anyone know a solution to this problem, I would appreciate it very much.
EDIT:
b = ruby_block...
...
end
b.run_action(:create)
Could possibly do the trick. It invokes the resource immediately.
The simplest answer to this is to not use chef attributes and not use ruby_block to do the work of talking to the REST API. The code can also be moved to a custom resource for better reuse:
unified_mode true
provides :my_resource
action :run do
cm = { :name => "web", :hostname => "" , :ip_addr => "" }
ca = { :name => "data", :hostname => "" , :ip_addr => "" }
cg = { :name => "gateway", :hostname => "" , :ip_addr => "" }
component_list = [cm, ca, cg]
hash = {}
for cmpnt in component_list
# do REST calls to external service to get cmpnt.hostname, ip_addr
# .......
hash[cmpnt.name] = {}
hash[cmpnt.name]['name'] = cmpnt.name
hash[cmpnt.name]['host'] = cmpnt.hostname
hash[cmpnt.name]['ip'] = cmpnt.ip_addr
end
template "/etc/app/configuration/config.xml" do
source "config.xml.erb"
variables( :dataHost => hash['data']['host'],
:webHost => hash['web']['host'],
:gatewayHost => hash['gateway']['host'] )
action :create
end
end
By using unified_mode and moving into a custom resource, it also makes it easier to use a node attribute without requiring the use of lazy {} or ruby_blocks. It also still allows chef configuration (like setting up resolv.conf or other network requirements before doing the REST calls) prior to calling this code while not having to think about compile/converge two pass issues in recipe context.
There is also no reason to use a resource like ruby_block to do pure ruby processing which does not change the system under management. In this case the ruby_block is hitting a REST service purely to collect data. That does not need to be placed into a Chef resource. It isn't clear from the question if that was being done because the questioner though it was a "best practice" (in this case it is not), or if it was being done to move execution to compile time in order to allow other chef resources that aren't part of the question to fire first (in which case using a custom resource is a much better solution than using a ruby_block).
It's been a while since this question, but in case someone is still looking for it, lazy evaluate is your friend:
template '/tmp/sql_file.sql' do
source "sql_file.sql.erb"
mode 0700
variables lazy {
# Create a new instance of MySQL library
mysql_lib = Acx::MySQL.new(
'127.0.0.1', 'root', node['mysql']['service']['pass']
)
password = node['mysql']['service']['support_admin']['ct_password']
# It returns the encrypted password after evaluate it, to
# be used in template variables
{ admin_password: mysql_lib.encrypted_password(password) }
}
end
https://docs.chef.io/resource_common.html#lazy-evaluation
I'm using a loop to load and execute Ruby scripts in a directory. At the moment the script will load the script, but how do I execute it when the only reference to it is the filename in the form of a string?
Dir.foreach('tests') do |item|
next if item == '.' or item == '..' #removes extra "." or ".."
load dirname + '/' + item #successfully loads the script
if item # the scripts return true/false
numberPassed+=1
else
numberFailed+=1
failed.push(item)
end
numberTested+=1
end
For some reason I'm getting 2 Passed, but it never actually runs the scripts "item" represents.
EDIT: here is an example of a script that would need to be loaded. They all follow this format:
require "watir-webdriver"
class TestScript
puts 'Testing etc etc"...'
browser = Watir::Browser.new :ie
browser.goto "webpage.htm"
browser.text_field(:name => "j_username").set "username"
browser.text_field(:name => "j_password").set "password"
browser.link(:id, "watSubmitLogin").click
browser.wait
browser.link(:id=> 'watCommDir').fire_event("onmouseover")
browser.link(:id=> 'watAddFi').click
browser.wait
...
browser.link(:href, "javascript: submitForm();").click
browser.wait
if browser.text.include?( 'The user already Exists')
puts 'Passed'
browser.close
return true
else
puts 'Failed'
browser.close
return false
end
end
I need to somehow tell the main script whether the sub-scripts pass or fail so I can keep track of how many pass/fail/error/total and create a report of all the tests that failed.
Looks like you are doing acceptance testing with Watir and try to do custom test results reporting.
I would recommend to use existing test runners to run all your tests and build custom output formatter for your needs. Existing test runners already solve a lot of issues you will encounter during creation of your own test runner (like how to run tests from specified folder, how to identify failing/successful test etc).
One of the commmon test runners for acceptance tests in Ruby community is Cucumber. Another good alternative is RSpec. Both these libraries support custom formatters:
In RSpec you would need to subclass RSpec::Core::Formatters::BaseFormatter.
In Cucumber you would need to implement class with methods specified in this documentation.
If you want to stay with the current simple implementation, here is one possible approach that is inpired by ruby Regexps: Inside the test set global variable, e.g. $test_succeeded (like $~, $& etc. global variables generated by ruby regular expressions) and then examine this value in your test runner.
In tests
if browser.text.include?( 'The user already Exists')
puts 'Passed'
browser.close
$test_succeeded = true
# ...
In tests runner
Dir.foreach('tests') do |item|
next if item == '.' or item == '..' #removes extra "." or ".."
load dirname + '/' + item #successfully loads the script
if $test_succeeded
# ...
If you have problems running the script then I can recommend to define special method to run tests (similar to RSpec approach):
def test
test_res = yield # call test
$test_results ||= {}
$test_results << test_res # and store its result in arra of test results
end
Then your tests will look like:
require 'file_with_test_method'
require 'watir-webdriver'
test do
# your test code
browser.text.include?( 'The user already Exists') # last expression in the block will be the test result
end
I've been really loving using contexts, subjects and its with rspec to really clean up my test code. Typical example:
context "as a user" do
subject{ Factory :user }
its(:name){ should == "Bob" }
end
What I can't figure out though is how I could make this condition dynamic (ie. based on other objects). its appears to instance eval the attribute within the block so I lose access to everything around it. I'd love to do something like:
its(:name){ should == subject.contact.name }
But I can't see any way of achieving this. Does anyone know if there is some a method proxied through to this instance eval that gives access to the origin object? Or if there's any other way I can use methods outside the scope of the instance of the attribute that I'm checking?
additional info
It seems as if subject within the its block gets changed to the actual attribute (name in this case)
Interestingly, I have access to any of my let methods, but again, I don't have access to my original subject in question.
You are almost answering yourself, use a let assignment before you set the subject. Then you can reference it everywhere:
context "as a user" do
let(:user) { Factory(:user) }
subject { user }
its(:name) { should == user.contact.name }
end
I'm a context, subject, its lover too !
Not sure if there's a way to do exactly what you're asking for since the "subject" within the block becomes the return value of User#name.
Instead, I've used the let method along with the following spec style to write this kind of test:
describe User do
describe '#name' do
let(:contact) { Factory(:contact, name: 'Bob') }
let(:user) { Factory(:user, contact: contact) }
subject { user.name }
it { should == 'Bob' }
end
end
This of course makes some assumptions about what your contact represents (here it's an association or similar). You may also choose to stub the return value of User#contact instead of relying on FactoryGirl to set up a "real" association.
Regardless of the choices you make on those fronts, this strategy has worked well for me. I find it allows me to be more concise about what is under test, while preserving info (for other devs & future self) about where the expected return value is coming from.
You can just set an instance variable within the subject block:
context 'as a user' do
subject { #user = FactoryGirl.create(:user) }
its(:name) { should == #user.name }
end