I'm trying to write a custom parser for my cucumber results. In doing so, I want to write rspec tests around it. What I currently have is as follows:
describe 'determine_test_results' do
it 'returns a scenario name as the key of the scenario results, with the scenario_line attached' do
pcr = ParseCucumberJsonReport.new
expected_results = {"I can login successfully"=>{"status"=>"passed", "scenario_line"=>4}}
cucumber_results = JSON.parse(IO.read('example_json_reports/json_passing.json'))
pcr.determine_test_results(cucumber_results[0]).should == expected_results
end
end
The problem is, determine_test_results has a sub method called determine_step_results, which means this is really an integration test between the 2 methods and not a unit test for determine_test_results.
How would I mock out the "response" from determine_step_results?
Assume determine_step_results returns {"status"=>"passed", "scenario_line"=>4}
what I have tried:
pcr.stub(:determine_step_results).and_return({"status"=>"passed", "scenario_line"=>6})
and
allow(pcr).to receive(:determine_step_results).and_return({"status"=>"passed", "scenario_line"=>6})
You could utilize stubs for what you're trying to accomplish. Project: RSpec Mocks 2.3 would be good reading regarding this particular case. I have added some code below as a suggestion.
describe 'determine_test_results' do
it 'returns a scenario name as the key of the scenario results, with the scenario_line attached' do
pcr = ParseCucumberJsonReport.new
expected_results = {"I can login successfully"=>{"status"=>"passed", "scenario_line"=>4}}
# calls on pcr will return expected results every time determine_step_results is called in any method on your pcr object.
pcr.stub!(:determine_step_results).and_return(expected_results)
cucumber_results = JSON.parse(IO.read('example_json_reports/json_passing.json'))
pcr.determine_test_results(cucumber_results[0]).should == expected_results
end
end
If all what determine_test_results does is call determine_step_results, you should not really test it, since it is trivial...
If you do decide to test it, all you need to test is that it calls the delegate function, and returns whatever is passed to it:
describe ParseCucumberJsonReport do
describe '#determine_test_results' do
it 'calls determine_step_results' do
result = double(:result)
input = double(:input)
expect(subject).to receive(:determine_step_results).with(input).and_return(result)
subject.determine_test_results(input).should == result
end
end
end
If it is doing anything more (like adding the result to a larger hash) you can describe it too:
describe ParseCucumberJsonReport do
describe '#determine_test_results' do
it 'calls determine_step_results' do
result = double(:result)
input = double(:input)
expect(subject).to receive(:determine_step_results).with(input).and_return(result)
expect(subject.larger_hash).to receive(:merge).with(result)
subject.determine_test_results(input).should == result
end
end
end
Related
I'm using the AfterStep hooks inside calabash-ios/cucumber.
I want to know the last executed step inside my hook.
AfterStep do |scenario|
puts "Step: #{scenario.name} #{scenario.title} #{scenario.gherkin_statement}"
end
I can see that the scenario is passed in, but how do I access the currently running step? I don't see any information inside the scenario docs regarding this.
I would assume that the step would be passed into the AfterStep hook. Any clues?
You may refer to this example code, which works with step indexes within the AfterStep hook.
Example:
CALABASH_COUNT = {:step_index => 0, :step_line => nil}
#TODO change this approach as it breaks scenario outlines
Before do |scenario|
begin
CALABASH_COUNT[:step_index] = 0
CALABASH_COUNT[:step_line] = scenario.raw_steps[CALABASH_COUNT[:step_index]].line
rescue Exception => e
puts "#{Time.now} - Exception:#{e}"
end
end
AfterStep do |scenario|
CALABASH_COUNT[:step_index] = CALABASH_COUNT[:step_index] + 1
raw = scenario.raw_steps[CALABASH_COUNT[:step_index]]
CALABASH_COUNT[:step_line] = raw.line unless raw.nil?
end
The Behave BDD framework, in Python allows a more simple step.name type accessor, but others seem more difficult, requiring the above technique of counting the current step and then using the index to find the name from the raw steps text.
I'm setting up Backburner as a work queue, and my job items need to return JSON for the resulting data they create. I'm not sure how to structure this. As a test I've tried doing:
class PrintJob
include Backburner::Performable
def self.print(text)
puts text
return "results"
end
end
Backburner.configure do |config|
config.beanstalk_url = ["beanstalk://127.0.0.1"]
# etc
end
val = PrintJob.async.print('some cool text')
puts val
and running Backburner.work inside IRB. The puts works but the return value comes back as true instead of "results".
Is there a way to get return values out of async methods? Or should I try a different approach, e.g. having one queue for jobs and another for results? If so, how can I associate the result 'job' with the original work it belongs to?
Note: I'm eventually using Sinatra and not Rails.
I'm in a situation where for our projects, there is a desire for 100% code coverage in our unit testing.
In order to support some dereference operations, I had to utilize the ability to chain dynamic method calls. So I ended up with this open class addition defined on one of my source files:
class Object
def call_method_chain(method_chain, arg=nil)
return self if method_chain.empty?
method_chain.split('.').inject(self) { |o,m|
if arg.nil?
o.send(m.intern)
else
o.send(m.intern, arg)
end
}
end
end
It works wonderfully. The problem is it's the only part of my application that isn't showing up as covered by unit tests, even though the area that calls the above method is covered.
I can't figure out how to cover the above with some unit tests.
I have no idea how much information is too much information here, but just to give an idea, here is the code that actually uses the above (in a file called data_setter.rb):
module Symbiont
module DataSetter
# #param data [Hash] the data to use
def using(data)
data.each do |key, value|
use_data_with(key, value) if object_enabled_for(key)
end
end
def use_data_with(key, value)
element = self.send("#{key}")
self.call_method_chain("#{key}.set", value) if element.class == Watir::TextField
self.call_method_chain("#{key}.set") if element.class == Watir::Radio
self.call_method_chain("#{key}.select", value) if element.class == Watir::Select
return self.call_method_chain("#{key}.check") if element.class == Watir::CheckBox and value
return self.call_method_chain("#{key}.uncheck") if element.class == Watir::CheckBox
end
private
def object_enabled_for(key)
web_element = self.send("#{key}")
web_element.enabled? and web_element.visible?
end
end
end
You can see using() calls use_data_with() and it's that latter method that relies on the call_method_chain() that I defined on Object.
Further, I have a unit test that exercises the using() method, which looks like this:
require 'spec_helper'
describe Symbiont::DataSetter do
include_context :page
include_context :element
it 'will set data in a text field' do
expect(watir_element).to receive(:visible?).and_return(true)
expect(watir_element).to receive(:enabled?).and_return(true)
expect(watir_browser).to receive(:text_field).with({:id => 'text_field'}).exactly(2).times.and_return(watir_element)
watir_definition.using(:text_field => 'works')
end
end
That test shows my logic calling the using() method. That test passes. When looking at my code coverage, the coverage of data_setter.rb is 100%. Yet the coverage report also shows that my call_method_chain() method is never being executed.
This is one of those cases where I fear strict adherence to unit testing coverage is causing more trouble than it's worth since I know the above logic is working.
EDIT (based on response from Neil):
I'm using SimpleCov for the coverage.
Regarding the coverage report and object, taking this:
1 class Object
2 def call_method_chain(method_chain, arg=nil)
3 return self if method_chain.empty?
4 method_chain.split('.').inject(self) { |o,m| #o.send(m.intern, arg) }
5 if arg.nil?
6 o.send(m.intern)
7 else
8 o.send(m.intern, arg)
9 end
10 }
11 end
12 end
The report says that lines 1 and 2 are covered. They show up as green. Starting from 3 down to 9, it's all red.
Regarding my spec_helper.rb file, this is what it looks like, at the top:
require 'simplecov'
require 'coveralls'
Coveralls.wear!
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
SimpleCov::Formatter::HTMLFormatter,
Coveralls::SimpleCov::Formatter
]
SimpleCov.start do
add_filter '/spec'
coverage_dir "#{SimpleCov.root}/spec/reports/coverage"
minimum_coverage 90
maximum_coverage_drop 5
end
require 'symbiont'
I have found that if I make a simple test like this:
it 'allows methods to be chained' do
methods = 'testing'.call_method_chain("reverse.capitalize")
end
That covers up to line 6 of my Object insertion. That leaves line 8, which I can't quite get to work. I tried this:
methods = 'testing'.call_method_chain("reverse.capitalize.eql?", 'GNITSET')
To no avail, however. So I can actually get it down to only one line not being covered, assuming I just use those little point tests like that. I'm not sure if that's in the spirit of good unit testing.
If I have a method like this:
require 'tweetstream'
# client is an instance of TweetStream::Client
# twitter_ids is an array of up to 1000 integers
def add_first_users_to_stream(client, twitter_ids)
# Add the first 100 ids to sitestream.
client.sitestream(twitter_ids.slice!(0,100))
# Add any extra IDs individually.
twitter_ids.each do |id|
client.control.add_user(id)
end
return client
end
I want to use rspec to test that:
client.sitestream is called, with the first 100 Twitter IDs.
client.control.add_user() is called with the remaining IDs.
The second point is trickiest for me -- I can't work out how to stub (or whatever) a method on an object that is itself a property of an object.
(I'm using Tweetstream here, although I expect the answer could be more general. If it helps, client.control would be an instance of TweetStream::SiteStreamClient.)
(I'm also not sure a method like my example is best practice, accepting and returning the client object like that, but I've been trying to break my methods down so that they're more testable.)
This is actually a pretty straightforward situation for RSpec. The following will work, as an example:
describe "add_first_users_to_stream" do
it "should add ids to client" do
bulk_add_limit = 100
twitter_ids = (0..bulk_add_limit+rand(50)).collect { rand(4000) }
extras = twitter_ids[bulk_add_limit..-1]
client = double('client')
expect(client).to receive(:sitestream).with(twitter_ids[0...bulk_add_limit])
client_control = double('client_control')
expect(client).to receive(:control).exactly(extras.length).times.and_return(client_control)
expect(client_control).to receive(:add_user).exactly(extras.length).times.and_return {extras.shift}
add_first_users_to_stream(client, twitter_ids)
end
end
n the .net world, my specs would follow the Arrange, Act, Assert pattern. I'm having trouble replicating that in rspec, because there doesn't appear to be an ability to selectively verify your mocks after the SUT has taken it's action. That, coupled with the fact that EVERY expectation is evaluated at the end of each 'It' block, is causing me to repeat myself in a lot of my specs.
Here's an example of what I'm talking about:
describe 'AmazonImporter' do
before(:each) do
Kernel.**stubs**(:sleep).with(1)
end
# iterates through the amazon categories, and for each one, loads ideas with
# the right response group, upserting ideas as it goes
# then goes through, and fleshes out all of the ideas that only have asins.
describe "find_new_ideas" do
before(:all) do
#xml = File.open(File.expand_path('../amazon_ideas_in_category.xml', __FILE__), 'r') {|f| f.read }
end
before(:each) do
#category = AmazonCategory.new(:name => "name", :amazon_id => 1036682)
#response = Amazon::Ecs::Response.new(#xml)
#response_group = "MostGifted"
#asin = 'B002EL2WQI'
#request_hash = {:operation => "BrowseNodeLookup", :browse_node_id => #category.amazon_id,
:response_group => #response_group}
Amazon::Ecs.**expects**(:send_request).with(has_entries(#request_hash)).returns(#response)
GiftIdea.expects(:first).with(has_entries({:site_key => #asin})).returns(nil)
GiftIdea.any_instance.expects(:save)
end
it "sleeps for 1 second after each amazon request" do
Kernel.**expects**(:sleep).with(1)
AmazonImporter.new.find_new_ideas(#category, #response_group)
end
it "loads the ideas for the given response group from amazon" do
Amazon::Ecs.**expects**(:send_request).
with(has_entries(#request_hash)).
returns(#response)
**AmazonImporter.new.find_new_ideas(#category, #response_group)**
end
it "tries to load those ideas from repository" do
GiftIdea.expects(:first).with(has_entries({:site_key => #asin}))
**AmazonImporter.new.find_new_ideas(#category, #response_group)**
end
In this partial example, I'm testing the find_new_ideas method. But I have to call it for each spec (the full spec has 9 assertion blocks). I further have to duplicate the mock setup so that it's stubbed in the before block, but individually expected in the it/assertion block. I'm duplicating or nearly duplicating a ton of code here. I think it's even worse than the highlighting indicates, because a lot of those globals are only defined separately so that they can be consumed by an 'expects' test later on. Is there a better way I'm not seeing yet?
(SUT = System Under Test. Not sure if that's what everyone calls it, or just alt.net folks)
You can use shared example groups to reduce duplication:
shared_examples_for "any pizza" do
it "tastes really good" do
#pizza.should taste_really_good
end
it "is available by the slice" do
#pizza.should be_available_by_the_slice
end
end
describe "New York style thin crust pizza" do
before(:each) do
#pizza = Pizza.new(:region => 'New York' , :style => 'thin crust' )
end
it_behaves_like "any pizza"
it "has a really great sauce" do
#pizza.should have_a_really_great_sauce
end
end
Another technique is to use macros, which is handy if you need similar specs in different classes.
Note: the example above is borrowed from The RSpec Book, Chapter 12.
you can separate them using "context" if that helps...
https://github.com/dchelimsky/rspec/wiki/faq