I'm writing some rspec tests for some web-pages. One of the pages contains several links that I want to test as a group. So my test looks something like this
require 'spec_helper'
t = Page.new
t.test do |t|
describe 'a thing' do
it 'should not be last' do
t.title
end
end
end
So when I call t.title I am actually calling that on the the following Page object being yielded (by itself) down below.
and my Page object looks like this
class Page
attr_accessor :driver
def initialize()
#driver = Watir::Browser.new :phantomjs
#home = ''
#driver.goto(#home)
end
def visit(url)
#driver.goto(url)
end
def title
#driver.title
end
def test
#subpages.each do |page|
visit(page)
yield self
end
end
end
So now when I run rspec, what ends up happening is the test will run as many times as I expect it to, however it runs each time it yields the object in the state it's in during the final iteration of visit. So it's not really testing the pages the way I want it to, it's testing the last page in the list.
Am I incorrectly using yield or self here? It seems pretty straightforward: pass the test as a block to the Page object's test method and have it run the test on itself.
Any tips? I'd like to be able to keep all the tests clean, and all the logic in the page object, but this is hindering me from doing so.
Within the scope of a given file, RSpec examples/tests don't get executed until they all have been defined. You're iterating through the page defining all these examples, but RSpec is collecting and not executing them until the iteration is complete, at which time the value of t remains unchanged and corresponds to the final state of the page.
Related
I often build little single-purpose Ruby scripts like this:
#!/usr/bin/env ruby
class Widget
def end_data
DATA.read
end
def render_data source_data
source_data.upcase
end
end
w = Widget.new
puts w.render_data(w.end_data)
__END__
data set to work on.
I'd like to include RSpec tests directly inside the file while I'm working on it. Something like this (which doesn't work but illustrates what I'm trying to do):
#!/usr/bin/env ruby
class Widget
def end_data
DATA.read
end
def render_data source_data
source_data.upcase
end
def self_test
# This doesn't work but shows what I'm trying to
# accomplish. The goal is to have RSpec run these type
# of test when self_test is called.
describe "Widget" do
it "should render data properly" do
#w = Widget.new
expect(#w.render_data('test string')).to eq 'TEST STRING'
end
end
end
end
w = Widget.new
w.self_test
__END__
data set to work on.
I understand this is not the normal way to work with RSpec and isn't appropriate in most cases. That said, there are times when it would be nice. So, I'd like to know, is it possible?
There are two things. First off rspec by default won't pollute the global namespace with methods like describe and so on. The second thing is that you need to tell rspec to run the specs after they've been declared.
If you change your self_test method to be
RSpec.describe "Widget" do
it "should render data properly" do
#w = Widget.new
expect(#w.render_data('test string')).to eq 'TEST STRING'
end
end
RSpec::Core::Runner.invoke
(having of course done require 'rspec' then that will run your specs).
The invoke methods exits the process after running the specs. If you don't want to do that, or need more control over where output goes etc. you might want to drop down to the run method which allows you to control these things.
I am running rspec tests on a catalog object from within a Ruby app, using Rspec::Core::Runner::run:
File.open('/tmp/catalog', 'w') do |out|
YAML.dump(catalog, out)
end
...
unless RSpec::Core::Runner::run(spec_dirs, $stderr, out) == 0
raise Puppet::Error, "Unit tests failed:\n#{out.string}"
end
(The full code can be found at https://github.com/camptocamp/puppet-spec/blob/master/lib/puppet/indirector/catalog/rest_spec.rb)
In order to pass the object I want to test, I dump it as YAML to a file (currently /tmp/catalog) and load it as subject in my tests:
describe 'notrun' do
subject { YAML.load_file('/tmp/catalog') }
it { should contain_package('ppet') }
end
Is there a way I could pass the catalog object as subject to my tests without dumping it to a file?
I am not very clear as to what exactly you are trying to achieve but from my understanding I feel that using a before(:each) hook might be of use to you. You can define variables in this block that are available to all the stories in that scope.
Here is an example:
require "rspec/expectations"
class Thing
def widgets
#widgets ||= []
end
end
describe Thing do
before(:each) do
#thing = Thing.new
end
describe "initialized in before(:each)" do
it "has 0 widgets" do
# #thing is available here
#thing.should have(0).widgets
end
it "can get accept new widgets" do
#thing.widgets << Object.new
end
it "does not share state across examples" do
#thing.should have(0).widgets
end
end
end
You can find more details at:
https://www.relishapp.com/rspec/rspec-core/v/2-2/docs/hooks/before-and-after-hooks#define-before(:each)-block
I am describing class on RSpec
class Pupil
def initialize(name, dateOfBirth)
#name = name
#dateOfBirth = dateOfBirth
end
def name
#name
end
def ages
#should calculate ages
end
end
describe Pupil do
context do
pupil = Pupil.new("Stanislav Majewski", "1 april 1999")
it "should returns name" do
pupil.name.should eq("Stanislav Majewski")
end
it "should calculates ages" do
#not described
end
end
end
RSpec returns:
..
Finished in 0.00203 seconds
2 examples, 0 failures
Is there an elegant way to display a failure message that the method is not described?
If you're concerned that you'll create a test and forget to put anything it in (sometimes I'll create three tests I know I'll need, and work on each of them in turn) then you can do the following:
it "should calculates ages" do
fail
end
OR
it "should calculates ages"
...and that's all (no block) will mark the test as pending automatically. In other words, don't fill out your tests until they have actual test code in them.
Also, if you don't test any assertions (i.e. if your spec doesn't contain any lines that have a call to should in them), your spec will appear to pass. This has happened to me a few times, where I write a new test, expecting it to fail, and it doesn't because I forgot to include the call to should which is what actually tests the assertion.
I'm iterating through a tree control on a webpage. Clicking on some nodes in the tree will change the content in FRAME_C, clicking on others will not. How do I filter a test to only run when the content has changed? Here's what I'm trying:
def viewDifferent?
if $prvView != $curView
return true
else
return false
end
end
...
describe "Exercising View" do
it "clicks a node in the tree control" do
$prvView = $b.frame( :id, 'FRAME_C').document.body.innertext
Timeout.timeout(50) do
spn.fire_event('onmouseup')
end
$curView = $b.frame( :id, 'FRAME_C').document.body.innertext
end
it "Runs only if the view is different", :if => viewDifferent? do
puts "Doing some stuff."
end
end
My problem is that RSpec is evaluating the filter for all of my tests before executing any of them. In the above example viewDifferent? will always (and does) return false since the two global variables have yet to be set by the previous test.
Is there a way to do what I'm asking? I've been trying to figure this out for days.
A test should always run. It should setup the state it requires to execute the code path you expect. It seems to me that executing tests conditionally based on the outcome of other tests totally breaks the spirits of the tests.
You should already know the previous view and the current view are different, and if are not what you expect you have a failure.
Every test should have a very specific path through your code you expect it to execute, and you should fail if it doesn't. There isn't a way to do what you want because you shouldn't do it that way.
I'm not familiar w/ rspec, but have you tried using a Proc? For example...
it "Runs only if the view is different", :if => lambda { viewDifferent? } do
puts "Doing some stuff."
end
A symbol as shorthand may even work...
it "Runs only if the view is different", :if => :viewDifferent? do
puts "Doing some stuff."
end
As you currently have it, it's calling the viewDifferent? method as soon as the test is declared. What you really want is to pass a Proc so that it gets called when the test is run.
I'm trying to make this test fail :)
it "should display the question" do
#ui.should_receive(:puts).with("What's your name?").once
#ui.ask_question("What's your name?")
end
At the moment it passes even if I don't call puts in my function.
Basically, #ui should call .puts on an object that probably defaults to $stdout. Then in your tests, you can replace $stdout with a StringIO object that you can set expectations on. This has the added benefit of making your #ui object more flexible.
Given the code:
require 'rubygems'
require 'spec'
class UI
def ask_question(q)
end
end
describe UI do
before do
#ui = UI.new
end
it "should display the question" do
#ui.should_receive(:puts).with("Whats your name?").once
#ui.ask_question("Whats your name?")
end
end
I get the failure:
F
1) Spec::Mocks::MockExpectationError in 'UI should display the question'
#<UI:0xb738effc> expected :puts with ("Whats your name?") once, but received it 0 times /home/avdi/tmp/puts_spec.rb:15:
Finished in 0.002575 seconds
1 example, 1 failure
What version of RSpec are you using?
You can try stringio or ZenTest, the following ruby-talk thread has more info.