Cucumber/Capybara - Using RSpec matchers with a "Page Object" pattern - ruby

I'm currently refactoring a whole load of cucumber tests to use a "Page Object" pattern, but I'm having a lot of problems using the RSpec matchers.
The existing step I have is as follows:
Then /^I should (not )?see the following alerts:$/ do |negate, alerts|
expectation = negate ? :should_not : :should
within(ALERT_TABLE_ID) do
alerts.hashes.each do |alert|
page.send(expectation, have_content(alert["Original ID"]))
end
end
end
My refactored step is:
Then /^I should (not )?see the following alerts:$/ do |negate, alerts|
expectation = negate ? :should_not : :should
#alert_reporting_panel = AlertReportingPanel.new(Capybara.current_session)
#alert_reporting_panel.verify_contents expectation, alerts
end
And my Panel Object is:
class AlertReportingPanel
def initialize(session)
#session = session
end
def verify_contents(expectation, alerts)
#session.within(ALERT_TABLE_ID) do
alerts.hashes.each do |alert|
#session.send(expectation, have_content(alert["Original ID"]))
end
end
end
end
Unfortunately, I get undefined method 'have_contents' for #<AlertReportingPanel:0x3f0faf8> (NoMethodError).
I have tried adding require 'rspec' to the top of the class and also tried fully qualifying the have-content method thus: Capybara::RSpecMatchers::HaveMatcher.have_content, but I just get uninitialized constant Capybara::RSpecMatchers (NameError).
I'm pretty new to Ruby and I'm sure this is trivial to fix... but I just can't seem to work it out for myself.
Please help. Thankyou.

This was a while back so I'm guessing you may have your answer by now but here goes.
You need to include the necessary modules in order bring in and have access to the likes of *have_content*. So your Panel Object would look like:
class AlertReportingPanel
include Capybara::DSL
include Capybara::Node::Matchers
include RSpec::Matchers
def initialize... etc

Instead of writing your own Page Object system you could try using SitePrism
I'm a little biased (I wrote that gem) but it might make life easier for you.

Related

How does Mocha generate a mock out of MyRubyClass.new in ruby?

My background is Java and I am new to Ruby. I saw a mocking/stubbing framework called Mocka. I saw this example test method:
require 'test/unit'
require 'mocha/test_unit'
class MiscExampleTest < Test::Unit::TestCase
# ...
def test_mocking_an_instance_method_on_a_real_object
product = Product.new
product.expects(:save).returns(true)
assert product.save
end
#...
end
What mechanism was used to "automatically" create a mock object of Person class (or object)? Not sure what to Google for.
If it was something like this
product = mock(Product.new)
I'd easily get it.
Thank You! :)
in general, this is referred to as "monkey patching".
ruby has the concept of open classes, so at runtime you can mess around with it.
in the specific case of mocha, i assume that it is this piece of code here: https://github.com/freerange/mocha/blob/a7bc1b53ace895503b4b5d4915382aead4632e3e/lib/mocha/api.rb#L18-L22

RSpec testing of a class which uses a gem object as an instance variable

So I'm pretty new to Rspec and I'm trying to figure out how to write tests for a class that takes an object as a constructor parameter and sets that object to an instance variable. Then it calls that instance variable's object methods in other methods.
Example:
class ClassA
def initialize(string_object, gem_object)
#instance_variable1 = gem_object
#string = string_object
end
def check_validity?(some_arg)
unless #instance_variable1.gemObjectMethod1.gemObjectMethod2(some_arg).empty?
return true
end
false
end
..
..
end
I feel very lost in how to write specifications for this. For one I don't really understand what specifying a constructor actually entails. What I realize is that I'd have to find some way of mocking or stubbing the gem_object I'm getting as argument, but I'm not sure how.
For the next method, what I've tried to this point is:
describe '#check_validity?' do
context 'gets empty list' do
let (:actual) { subject.check_validity?("sample") }
before do
allow(subject).to receive(#instance_variable1.gemObjectMethod1.gemObjectMethod2).with("sample").and_return([])
end
it 'returns false' do
expect(actual).to be false
end
end
end
But this gives me error relating to my constructor saying that it expected 2 arguments but was given 0.
Any help would be much appreciated! Also, I couldn't really find anything on line about specifying constructors with their arguments mocked. Maybe I'm looking in the wrong place or maybe missing something obvious as this is my first experience with BDD.
In RSpec, 'receive' is a method that accepts a symbol that represents the name of a method. (It allows you to chain a 'with' method that accepts the expected list of parameters.) To fix the before-block you could do this:
before do
allow(subject.instance_variable_get(:#instance_variable1).gemObjectMethod1).to receive(:gemObjectMethod2).with("sample").and_return([])
end
The sheer ugliness of that, though, suggests that something is wrong. And it is. The code is violating the law of demeter pretty badly and the test is being drawn into it.
As a first attempt to clean it up, you might consider a method that "caches" the results of calling #instance_variable1.gemObjectMethod1. Let's say that that first method returns an enumerable group of widgets. You could change your class to include something like this:
def check_validity(a_string)
widgets.gemObjectMethod2(a_string).empty?
end
private
def widgets
#widgets ||= #instance_variable1.gemObjectMethod1
end
Your class still knows a bit too much about the gem object, but now you have broken it down in such a way that you could refactor how you find widgets -- perhaps a different gem or your own implementation of it. For the purposes of your testing, you can isolate that decision from the test by mocking widgets.
let(:gem_widgets) do
instance_double(GemObjectMethod1ResultClass, gemObjectMethod2: true)
end
before do
allow(subject).to receive(:widgets).and_return(gem_widgets)
allow(gem_widgets).to receive(:gemObjectMethod2).with("sample").
and_return([])
end
it 'should pass with "sample"' do
expect(actual).to eql true
end

Namespace issue in Rspec while using the let() helper function

I'm having an issue with using the let function provided by rspec:
In app/spec/class_spec.rb:
require 'spec_helper'
module App
describe Class do
let(:instance) {Class.new('param')}
describe "#method" do
it "does something" do
instance.method(...)
# clever test code
end
.
.
.
end
I've been following the RSpec book, and according to its examples (which worked through!) let should give me access to the variable instance for the rest of the 'describe Class' block. But I get the error:
Uninitialized constant App::instance
I also tried adding this code to no avail:
before :all do
instance
end
What am I doing wrong?
Instead of
module App
describe Class do
just use:
describe App::Class
and everything should be fine
hint: instead let(:instance) {Class.new('param')} you could write let(:instance) {described_class.new('param')}. It's more clever.

Kaminari and Capybara conflict

I seem to have some sort of conflict between the page method of capybara and the page method of Kaminari.
That's what I guessed, anyway, here is the error :
Failure/Error: before { sign_in_as user }
ActionView::Template::Error:
wrong number of arguments (1 for 0)
# ./app/models/feed.rb:9:in `microposts'
[Rest of the backtrace]
The code sample :
class Feed
def microposts(opts = { urgent: false })
urgent = opts[:urgent]
p Microposts.where(id: 1).page # <Capybara::Session>
p Microposts.where(id: 1).page(1) # Error
end
end
If I remove the pagination, the test works fine.
I don't understand how this is possible, I guess Capybara is adding the "page" method to the Object scope, but as Kaminari add its page method to ActiveRecord::Base (if I recall correctly) it should override Capybara's one.
I did not see anyone having this kind of trouble, how is it possible ?
Thanks.
I had the same problem with Capybara 2.x
My feature specs are in the spec/feature directory. I realised from reading the Capybara documentation that there is no need to include the Capybara::DSL in your spec_helper if your using the features directory. It's already included.
There is a warning given if you include Capybara::DSL in the spec_helper that it will pollute the global namespace and this is exactly why it's a bad idea!
Check out this rspec-rails page on Capybara for details
This is a little bit of a hack but I was able to work around the problem (where Capybara 'pollutes' the object space) by undef-ing the method in my spec:
# Capybara adds a 'page' method to the Object class which conflicts with the Kaminari scope
# Remove it here to allow things to work
Object.send :undef_method, :page
I have traced back where this is happening and essentially:
The #page method comes from Capybara::DSL
The Capybara::DSL method is included into the Object class via RSpec's #configure.include method (see lib/capybara/rspec.rb).
RSpec then includes it into the 'group', however I believe this is where it drops into Object.
The solution here might just be to change the name of the method in Capybara, but I guess thats not a decision I'm willing to make :)

Is there a way to undo Mocha stubbing of any_instance in Test::Unit

Much like this question, I too am using Ryan Bates's nifty_scaffold. It has the desirable aspect of using Mocha's any_instance method to force an "invalid" state in model objects buried behind the controller.
Unlike the question I linked to, I'm not using RSpec, but Test::Unit. That means that the two RSpec-centric solutions there won't work for me.
Is there a general (ie: works with Test::Unit) way to remove the any_instance stubbing? I believe that it's causing a bug in my tests, and I'd like to verify that.
As it happens, Mocha 0.10.0 allows unstubbing on any_instance().
str = "Not Stubbed!"
String.any_instance.stubs(:to_s).returns("Stubbed!")
puts str.to_s # "Stubbed!"
String.any_instance.unstub(:to_s)
puts str.to_s # "Not Stubbed!"
Mocha does not provide such a functionality. However you can implement it yourself.
The first thing we should know about mocha is that mocha actually replaces the original methods when you stub them. So in order to be able to restore these methods later, you must keep a reference to the former ones. It can be easily achieved by: alias new_method old_method.
It must be done before mocking the old_method.
Now, to unmock a method, you only need to alias old_method new_method.
Consider the following code:
class A
def a
true
end
end
class TestA < Test::Unit::TestCase
def test_undo_mock
a = A.new
A.class_eval {alias unmocked_a a}
A.any_instance.stubs(:a).returns("b")
assert a.a, "b"
A.class_eval {alias a unmocked_a}
assert a.a, "a"
end
end
If you want to remove all your stubs/expectations in one go, then you can do that using mocha_teardown (eg. call self.mocha_teardown).
May be a little bit destructive in this case, however.

Resources