'Weight converter'
Trying to get the hang of using stubs but I can't get the format right, what do I have wrong? I know I already have the method in the code in this case but I am trying to learn how to do a stub correctly.
Test:
describe "Convert Pounds to Kilograms" do
it "should convert 3lb to 1kg" do
weight = WeightConverter.stub!(:convert).with(3, 'lbs_to_kgs').and_return(1)
weight.should == 1
end
Code:
class WeightConverter
def self.convert(from, what_to_what)
if what_to_what == 'lbs_to_kgs'
(from / 2.2).truncate
elsif what_to_what == 'kgs_to_lbs'
(from * 2.2).truncate
end
end
end
fyi - this works (without stubs)
it "should convert 91lbs to 41kgs" do
weight = WeightConverter.convert(91, 'lbs_to_kgs')
weight.should == 41
end
Error:
Failures:
1) Convert Pounds to Kilograms should convert 3lb to 1kg
Failure/Error: weight.should == 1
expected: 1
got: #<Proc:0x000000010b0468#/home/durrantm/.rvm/gems/ruby-1.9.3-p125/gems/rspec-mocks-2.10.1/lib/rspec/mocks/message_expectation.rb:459 (lambda)> (using ==)
# ./weight_converter_spec.rb:19:in `block (2 levels) in <top (required)>'
Finished in 0.00513 seconds
7 examples, 1 failure
You don't want to assign to the stub, rather you should be doing something like this:
it "should convert 3lb to 1kg" do
WeightConverter.stub!(:convert).with(3, 'lbs_to_kgs').and_return(1)
weight = WeightConverter.convert(3, 'lbs_to_kgs')
weight.should == 1
end
However, that's a fairly useless test -- the only thing it's testing is that your stub/mock library does what it's supposed to (i.e. it's not actually testing WeightConverter at all). Since you're directly testing the internals of WeightConverter, you don't need to stub it. You should be using actual values like in your second example. However, if WeightConverter depended on another class, you might stub that other class.
Related
I have scenario when upon login to the page, I am presented with numerous profile(can be 1 to 5).
I am looking for specific profile based by tn number.
I locate element that represent tn and then put in array to search for all available elements with same to locate correct profile in order to click on it.
Here is the code:
And(/^I look for "([^"]*)"$/) do |number|
elements = #driver.find_elements(:css => "h3.phone-number")
elements.each do |element|
renewals_page.select_profile.click if element.text == #config[number]
return element
end
fail
end
I am passing desired number from yaml file depends on the account.
renewals_page.select_profile.click is defined in another file as method
def select_profile
#driver.find_element(:css => "h3.phone-number")
end
So when I try to locate that element and click on it, I get following error
unexpected return (LocalJumpError)
./features/step_definitions/renewals_login_step.rb:28:in `block (2 levels) in <top (required)>'
./features/step_definitions/renewals_login_step.rb:26:in `each'
./features/step_definitions/renewals_login_step.rb:26:in `/^I look for "([^"]*)"$/'
Remove return from here:
renewals_page.select_profile.click if element.text == #config[number]
# ⇓⇓⇓⇓⇓⇓
# return element # removed
element # proper working return from lambda: result of last line
end
the reason is that return keyword may be used to return from method only, not from lambda.
I'm trying to create helper to test JSON responses in uniform and nice way.
For more descriptive and specific failures I want to generate one example per JSON atom.
Example syntax:
RSpec.describe "some JSON API View" do
setup_objects
before { render }
describe "response" do
subject { rendered }
it_conforms_to_json(
id: 27,
email: "john.smith#example.com",
name: "John",
profile_description: %r{professional specialist},
nested: {
id: 37,
status: "rejected"
}
)
end
end
So this snippet will be an equivalent to:
RSpec.describe "some JSON API View" do
setup_objects
before { render }
describe "response" do
subject { rendered }
it 'has equal value at object["id"]' do
expect(subject["id"]).to eq(27)
end
it 'has equal value at object["email"]' do
expect(subject["email"]).to eq("john.smith#example.com")
end
it 'has equal value at object["name"]' do
expect(subject["name"]).to eq("John")
end
it 'has matching value at object["profile_description"]' do
expect(subject["profile_description"]).to match(%r{professional specialist})
end
it 'has equal value at object["nested"]["id"]' do
expect(subject["nested"]["id"]).to eq(37)
end
it 'has equal value at object["nested"]["status"]' do
expect(subject["nested"]["status"]).to eq("rejected")
end
end
end
I was able to achieve that easily with this snippet:
module JsonHelper
extend self
def it_conforms_to_json(json)
generate_examples_for(json)
end
private
def generate_examples_for(json, opts)
with_prefix = opts.fetch(:with_prefix, [])
if json.is_a?(Hash)
json.each do |key, new_json|
new_prefix = with_prefix + [key.to_s]
generate_examples_for(new_json, opts.merge(with_prefix: new_prefix))
end
elsif json.is_a?(Array)
raise NotImplemented.new("Arrays are not allowed yet")
elsif json.is_a?(String) || json.is_a?(Numeric)
it "is expected to have equal value at json[\"#{with_prefix.join('"]["')}\"]" do
value = JSON.parse(subject)
with_prefix.each { |key| value = value[key.to_s] }
expect(value).to eq(json)
end
end
end
end
And just enabling it by extending it: rspec_config.extend JsonHelper
Obvious problem starts when you think about pretty failure messages:
They show backtrace including location of it "is expected...bla-bla-bla
They exclude location of it_conforms_to_json(...) call
First is fixable by adding backtrace exclusion/clean pattern, but it results in a full backtrace because everything is filtered.
Second and previous is fixable by wrapping expect statement in begin..rescue, mangling with backtrace by prepending it with file_path:line of it_conforms_to_json(...) call and re-raising modified exception.
After first 2 problems are being resolved, the new one arises:
"Unable to find matching line from the backtrace"
Suspected method is find_first_non_rspec_line (or something similar), that finds first line in a backtrace of it call (not exception backtrace) by applying default rspec exclusion regex'es.
It is fixable by mangling with RSpec internals, i.e.:
For rspec 2: monkey patch method that applies this regex'es
For rspec 3: undefine constant LIB_REGEX and define it again adding this helper files to this regex
For rspec 3 (recent minor/patch versions): the same, but with IGNORE_REGEX
Code of this fixup becomes shitty and unreadable, and it will be hell to maintain, because it depends on rspec inner implementation, that can change from minor/patch version to version. Who interested in reading this ugly thing here
More that that, it acts differently for rspec 2 and rspec 3.
rspec 2, perfectly what is required:
Failures:
1) A json response is expected to have equal value at json["id"]
Failure/Error: it_conforms_to_json(
expected: 37
got: 25
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
2) A json response is expected to have equal value at json["name"]
Failure/Error: it_conforms_to_json(
expected: "Smith"
got: "John"
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
rspec 3, slightly off, but still acceptable:
Failures:
1) A json response is expected to have equal value at json["id"]
Failure/Error: expect(value).to eq(json)
expected: 37
got: 25
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
# ./lib/rspec/json_expectations/expectations.rb:32:in `block in generate_examples_for'
2) A json response is expected to have equal value at json["name"]
Failure/Error: expect(value).to eq(json)
expected: "Smith"
got: "John"
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
# ./lib/rspec/json_expectations/expectations.rb:32:in `block in generate_examples_for'
So here are the questions:
Is there any built-in public-API means of achieving it?
It seems I have problems with naming here, it is not expectations, but what...?
So, it is a bit ugly now (first iteration, though), but works. No mangling with RSpec internals, using only public RSpec API, with nice error messages, without even cleaning a backtrace:
module RSpec
module JsonExpectations
class JsonTraverser
def self.traverse(errors, expected, actual, prefix=[])
if expected.is_a?(Hash)
expected.map do |key, value|
new_prefix = prefix + [key]
if actual.has_key?("#{key}")
traverse(errors, value, actual["#{key}"], new_prefix)
else
errors[new_prefix.join("/")] = :no_key
false
end
end.all?
elsif expected.is_a?(String) || expected.is_a?(Numeric)
if actual == expected
true
else
errors[prefix.join("/")] = {
actual: actual,
expected: expected
}
false
end
else
raise NotImplementedError, "#{expected} expectation is not supported"
end
end
end
end
end
RSpec::Matchers.define :include_json do |expected|
match do |actual|
unless expected.is_a?(Hash)
raise ArgumentError, "Expected value must be a json for include_json matcher"
end
RSpec::JsonExpectations::JsonTraverser.traverse(
#include_json_errors = {},
expected,
JSON.parse(actual)
)
end
# RSpec 2 vs 3
send(respond_to?(:failure_message) ?
:failure_message :
:failure_message_for_should) do |actual|
res = []
#include_json_errors.each do |json_path, error|
res << %{
json atom on path "#{json_path}" is missing
} if error == :no_key
res << %{
json atom on path "#{json_path}" is not equal to expected value:
expected: #{error[:expected].inspect}
got: #{error[:actual].inspect}
} if error.is_a?(Hash) && error.has_key?(:expected)
end
res.join("")
end
end
It allows this syntax now:
it "has basic info about user" do
expect(subject).to include_json(
id: 37,
email: "john.smith#example.com",
name: "Smith"
)
end
It generates errors like this:
F
Failures:
1) A json response has basic info about user
Failure/Error: expect(subject).to include_json(
json atom on path "id" is not equal to expected value:
expected: 37
got: 25
json atom on path "name" is not equal to expected value:
expected: "Smith"
got: "John"
# ./spec/simple_with_fail_spec.rb:11:in `block (2 levels) in <top (required)>'
Finished in 0.00065 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/simple_with_fail_spec.rb:10 # A json response has basic info about user
Works for both RSpec 2 and 3.
If somebody interested it is here https://github.com/waterlink/rspec-json_expectations
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.
I've tried to refactor the old piece of tests. The code below:
describe "translation result", :js => true do
it "translations should be shown as links to translations in second way" do
visit '/'
fill_in('query-field', :with => 'kOŃ')
click_button('search-button')
sleep(7)
page.all(:css, '.result a').size.should eq(2)
page.find('.result a').text.should == 'horse'
end
end
return information below:
1) translation result translations should be shown as links to translations in second way
Failure/Error: page.find('.result a').text.should == 'horse'
Capybara::Ambiguous:
Ambiguous match, found 2 elements matching css ".result a"
# ./spec/integration/result_spec.rb:12:in `block (2 levels) in <top (required)>'
I tried to get a element from what is returned by 'find method' in describe block. It means I tried to do it as below shown:
page.find('.result a').first.text.should == 'horse'
or
page.find('.result a')[0].text.should == 'horse'
I did it because I think when I got two elements then I can get one of them. Do you think my logic is correct ? How to resolve the problem. The repo with code is here: https://github.com/mtczerwinski/dict-app
You can just use all in place of find if you really want to find multiple matches:
page.all('.result a').first.text.should == 'horse'
See http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Finders#all-instance_method
I am using the Mongo Ruby driver and have this block of Ruby code before and after line 171 in my code, which is apparently the source of the error below it (the query.each line is line 171):
query = get_callback.call( "databases", id )
if query.count > 0
puts query.count.inspect + " results: " + query.inspect
res = {}
query.each do |result|
puts result.inspect
end
else
puts "No results" + res.inspect
res = {}
end
The error:
1 results: <Mongo::Cursor:0x3fc15642c154 namespace='myproj.databases' #selector={"_id"=>BSON::ObjectId('4fe120e4a2f9a386ed000001')} #cursor_id=>
TypeError - can't convert Mongo::Cursor into Integer:
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/bson-1.6.4/lib/bson/byte_buffer.rb:156:in `pack'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/bson-1.6.4/lib/bson/byte_buffer.rb:156:in `put_int'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/mongo-1.6.4/lib/mongo/cursor.rb:603:in `construct_query_message'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/mongo-1.6.4/lib/mongo/cursor.rb:466:in `send_initial_query'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/mongo-1.6.4/lib/mongo/cursor.rb:459:in `refresh'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/mongo-1.6.4/lib/mongo/cursor.rb:128:in `next'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/mongo-1.6.4/lib/mongo/cursor.rb:291:in `each'
/Users/myuser/Code/myproj/my_file.rb:171:in `block in initialize'
My query object: {"_id"=>BSON::ObjectId('4fe120e4a2f9a386ed000001')}
I have not the faintest idea what's causing this. I've verified the object I'm finding exists and the query.count shows that there's a result in my Mongo::Cursor.
I've not found any examples of the issue on Google and every Mongo/Ruby on the web I've found uses an each iterator just like I do. Anyone know what's the cause of this error? I notice I also get it when trying to use to_a to cast the collection to a JSON-usable object.
For what it's worth, here's the relevant part of byte_buffer.rb is below. The line with << is line 156.
def put_int(i, offset=nil)
#cursor = offset if offset
if more?
#str[#cursor, 4] = [i].pack(#int_pack_order)
else
ensure_length(#cursor)
#str << [i].pack(#int_pack_order)
end
#cursor += 4
end
This happens when you pass nil to a Ruby Mongo driver limit() method.