RSpec: How do you implicitly filter tests based on the outcome of a previous test? - ruby

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.

Related

Yielding self to rspec test inside each loop yields last known state

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.

rspec way for passing variable between multiple contexts

I was wondering what would be the best way to pass variable between multiple contexts (or multiple its) in rspec but without using global variables?
For example, I have this:
describe "My test" do
let(:myvar) { #myvar = 0 }
context "First test pass" do
it "passes" do
myvar = 20
expect(myvar).to eq(20)
end
end
context "Second test pass" do
it "passes" do
expect(myvar).to eq(20)
end
end
end
Now, obviously, this will not work with let because with new context, myvar variable will be back on initial state which is = 0.
I would need mechanism to "cache state" between two contexts which would in turn give me value of myvar = 20 in second context
Any opinions, suggestions and improvements are welcome.
Thanks
Another simple way, would be to define a 'local variable' in describe context.
the 'local variable' would live throughout the describe, and any changes during run time would effect it, and so change it.
For example
describe 'tests' do
context 'Sharing a variable across tests' do
var = 1
puts var
it "it one. var = #{var}" do
var = var*2
puts var
end
it "it two" do
puts var
end
end
end
Output
1
2
1
What happens is not what you think happens.
What you want to happen break "unit testing" as a methodology.
Let me explain #2 first - unit testing test cases should be able to work in isolation, which means that they should work when run together, when run apart, and in any order... so much so that some unit testing frameworks (like the default one in elixir) run test cases in parallel...
As for #1 - when you write myvar = 20 you are not assigning a value to let(:myvar) { #myvar = 0 }, you simply create a local variable, which will override all calls to myvar within the method, but will not be available outside the method (myvar will return 0).
Even if you would have set #myvar = 20 (unless you do it before you call myvar for the first time) instead, myvar would still return 0, since the let function is using a memento pattern, which means it is called once, and subsequent calls return the value originally returned (in this case 0):
puts myvar
#myvar = 20
puts myvar
# => 0
# => 0
I just ran into this same problem. How I solved it was by using factory_girl gem.
Here's the basics:
create a factory:
require 'factory_girl'
require 'faker' # you can use faker, if you want to use the factory to generate fake data
FactoryGirl.define do
factory :generate_data, class: MyModule::MyClass do
key 100 # doesn't matter what you put here, it's just a placeholder for now
another_key 'value pair'
end
end
Now after you made the factory you need to make a Model that looks like this:
Module MyModule
class MyClass
#for every key you create in your factory you must have a corresponding attribute accessor in the model.
attr_accessor :key, :another_key
#you can also place methods here to call from your spec test, if you wish
# def self.test
#some test
# end
end
end
Now going back to your example you can do something like this:
describe "My test" do
let(:myvar) { #myvar }
context "First test pass" do
it "passes" do
#myvar.key = 20 #when you do this you set it now from 100 to 20
expect(#myvar.key).to eq(20)
end
end
context "Second test pass" do
it "passes" do
expect(#myvar.key).to eq(20) #it should still be 20 unless you overwrite that variable
end
end
end
As stated by others, not proper way of unit testing. But, how should we know if you're unit testing or not. So, I won't judge.
Anyways, good luck let us know, if you got some other solution!

How to use yield self within Rspec

This is a description of how to create a helper method in Rspec taken from the Rspec book (page 149). This example assumes that there is a method called 'set_status' which is triggered when the 'Thing' object is created.
Both sets of code create a new 'Thing' object, set the status, then do 'fancy_stuff'. The first set of code is perfect clear to me. One of the 'it' statements it triggered, which then calls the 'create_thing' method with options. A new 'Thing' object is created and the 'set_status' method is called with the 'options' attribute as the parameter.
The second set of code is similar. One of the 'it' statements is triggered, which then calls the 'given_thing_with' method while passing ':status' hash assignment as a parameter. Within the 'given_thing_with' method the 'yield' is triggered taking the 'Thing.new' as a parameter. This is where I am having trouble. When I try to run this code I get an error of "block given to yield". I understand that whatever attributes that are passed by yield will be returned to the 'thing' in pipe brace from the 'it' statement that called the 'given_thing_with' method. I can get the new
What I don't understand is why the code block is not called in the 'given_thing_with' method after the 'yield' command. In other words, I can't code in that block to run.
Thanks in advance for your help.
The remainder of this question is quoted directly from the Rspec book:
describe Thing do
def create_thing(options)
thing = Thing.new
thing.set_status(options[:status])
thing
end
it "should do something when ok" do
thing = create_thing(:status => 'ok')
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
it "should do something else when not so good" do
thing = create_thing(:status => 'not so good')
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
One idiom you can apply to clean this up even more is to yield self from initializers in your objects. Assuming that Thing's initialize() method does this and set_status() does as well, you can write the previous like this:
describe Thing do
def given_thing_with(options)
yield Thing.new do |thing|
thing.set_status(options[:status])
end
end
it "should do something when ok" do
given_thing_with(:status => 'ok') do |thing|
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
it "should do something else when not so good" do
given_thing_with(:status => 'not so good') do |thing|
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
end
The example in the book is a bit confusing because the implementation of Thing is not shown. To make this work you need to write Thing like so:
class Thing
def initialize
yield self
end
end
When given_thing_with is called it yields a new Thing, which will yield itself when it is constructed. This means that when the inner code block (the one containing thing.set_status) is executed it will have a reference to he newly built Thing.
There are 2 issues with the code from book.
1. Setting up the initializer to yield itself
When the Thing object is created, it needs an initializer and need yield itself.
class Thing
def initialize
yield self
end
end
However, this alone will still causes an error, at least on my system, which is Ruby 1.9.3. Specifically, the error is 'block given to yield (SyntaxError)'. This doesn't make much sense, since that is what we want it to do. Regarless, that is the error I get.
2. Fixing the 'block given to yield' error
This is not as obvious and has something to do with either Ruby or the 'yield' statement, but creating a block using 'do...end' as was written in the book and is shown below causes the error.
yield Thing.new do |thing|
thing.set_status(options[:status])
end
Fixing this error is simlpy a matter of creating the block using braces, '{...}', as is shown below.
yield Thing.new { |thing|
thing.set_status(options[:status])
}
This is not good form for multiline Ruby code, but it works.
Extra. How the series of yields works to set the parameters of the 'Thing' object
The problem is already fixed, but this explains how it works.
the "caller block" calls 'given_thing_with' method with a parameter
that method yields back to the "caller block" a new "Thing" and a block (I'll call it the "yield block")
to execute the "yield block", the Thing class needs the initialization and 'yield self', otherwise the 'set_status' method will never be run because the block will be ignored
the new "Thing" is already in the "caller block" and has it's status set and now the relevant method is executed

Correct way to TDD methods that calls other methods

I need some help with some TDD concepts. Say I have the following code
def execute(command)
case command
when "c"
create_new_character
when "i"
display_inventory
end
end
def create_new_character
# do stuff to create new character
end
def display_inventory
# do stuff to display inventory
end
Now I'm not sure what to write my unit tests for. If I write unit tests for the execute method doesn't that pretty much cover my tests for create_new_character and display_inventory? Or am I testing the wrong stuff at that point? Should my test for the execute method only test that execution is passed off to the correct methods and stop there? Then should I write more unit tests that specifically test create_new_character and display_inventory?
I'm presuming since you mention TDD the code in question does not actually exist. If it does then you aren't doing true TDD but TAD (Test-After Development), which naturally leads to questions such as this. In TDD we start with the test. It appears that you are building some type of menu or command system, so I'll use that as an example.
describe GameMenu do
it "Allows you to navigate to character creation" do
# Assuming character creation would require capturing additional
# information it violates SRP (Single Responsibility Principle)
# and belongs in a separate class so we'll mock it out.
character_creation = mock("character creation")
character_creation.should_receive(:execute)
# Using constructor injection to tell the code about the mock
menu = GameMenu.new(character_creation)
menu.execute("c")
end
end
This test would lead to some code similar to the following (remember, just enough code to make the test pass, no more)
class GameMenu
def initialize(character_creation_command)
#character_creation_command = character_creation_command
end
def execute(command)
#character_creation_command.execute
end
end
Now we'll add the next test.
it "Allows you to display character inventory" do
inventory_command = mock("inventory")
inventory_command.should_receive(:execute)
menu = GameMenu.new(nil, inventory_command)
menu.execute("i")
end
Running this test will lead us to an implementation such as:
class GameMenu
def initialize(character_creation_command, inventory_command)
#inventory_command = inventory_command
end
def execute(command)
if command == "i"
#inventory_command.execute
else
#character_creation_command.execute
end
end
end
This implementation leads us to a question about our code. What should our code do when an invalid command is entered? Once we decide the answer to that question we could implement another test.
it "Raises an error when an invalid command is entered" do
menu = GameMenu.new(nil, nil)
lambda { menu.execute("invalid command") }.should raise_error(ArgumentError)
end
That drives out a quick change to the execute method
def execute(command)
unless ["c", "i"].include? command
raise ArgumentError("Invalid command '#{command}'")
end
if command == "i"
#inventory_command.execute
else
#character_creation_command.execute
end
end
Now that we have passing tests we can use the Extract Method refactoring to extract the validation of the command into an Intent Revealing Method.
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
if command == "i"
#inventory_command.execute
else
#character_creation_command.execute
end
end
def invalid?(command)
!["c", "i"].include? command
end
Now we finally got to the point we can address your question. Since the invalid? method was driven out by refactoring existing code under test then there is no need to write a unit test for it, it's already covered and does not stand on it's own. Since the inventory and character commands are not tested by our existing test, they will need to be test driven independently.
Note that our code could be better still so, while the tests are passing, lets clean it up a bit more. The conditional statements are an indicator that we are violating the OCP (Open-Closed Principle) we can use the Replace Conditional With Polymorphism refactoring to remove the conditional logic.
# Refactored to comply to the OCP.
class GameMenu
def initialize(character_creation_command, inventory_command)
#commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
#commands[command].execute
end
def invalid?(command)
!#commands.has_key? command
end
end
Now we've refactored the class such that an additional command simply requires us to add an additional entry to the commands hash rather than changing our conditional logic as well as the invalid? method.
All the tests should still pass and we have almost completed our work. Once we test drive the individual commands you can go back to the initialize method and add some defaults for the commands like so:
def initialize(character_creation_command = CharacterCreation.new,
inventory_command = Inventory.new)
#commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
The final test is:
describe GameMenu do
it "Allows you to navigate to character creation" do
character_creation = mock("character creation")
character_creation.should_receive(:execute)
menu = GameMenu.new(character_creation)
menu.execute("c")
end
it "Allows you to display character inventory" do
inventory_command = mock("inventory")
inventory_command.should_receive(:execute)
menu = GameMenu.new(nil, inventory_command)
menu.execute("i")
end
it "Raises an error when an invalid command is entered" do
menu = GameMenu.new(nil, nil)
lambda { menu.execute("invalid command") }.should raise_error(ArgumentError)
end
end
And the final GameMenu looks like:
class GameMenu
def initialize(character_creation_command = CharacterCreation.new,
inventory_command = Inventory.new)
#commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
#commands[command].execute
end
def invalid?(command)
!#commands.has_key? command
end
end
Hope that helps!
Brandon
Consider refactoring so that the code that has responsibility for parsing commands (execute in your case) is independent of the code that implements the actions (i.e., create_new_character, display_inventory). That makes it easy to mock the actions out and test the command parsing independently. You want independent testing of the different pieces.
I would create normal tests for create_new_character and display_inventory, and finally to test execute, being just a wrapper function, set expectations to check that the apropriate command is called (and the result returned). Something like that:
def test_execute
commands = {
"c" => :create_new_character,
"i" => :display_inventory,
}
commands.each do |string, method|
instance.expects(method).with().returns(:mock_return)
assert_equal :mock_return, instance.execute(string)
end
end

How to run arbitrary object method from string in ruby?

So I'm fairly new to ruby in general, and I'm writing some rspec test cases for an object I am creating. Lots of the test cases are fairly basic and I just want to ensure that values are being populated and returned properly. I'm wondering if there is a way for me to do this with a looping construct. Instead of having to have an assertEquals for each of the methods I want to test.
For instace:
describe item, "Testing the Item" do
it "will have a null value to start" do
item = Item.new
# Here I could do the item.name.should be_nil
# then I could do item.category.should be_nil
end
end
But I want some way to use an array to determine all of the properties to check. So I could do something like
propertyArray.each do |property|
item.#{property}.should be_nil
end
Will this or something like it work? Thanks for any help / suggestions.
object.send(:method_name) or object.send("method_name") will work.
So in your case
propertyArray.each do |property|
item.send(property).should be_nil
end
should do what you want.
If you do
propertyArray.each do |property|
item.send(property).should be_nil
end
within a single spec example and if your spec fails then it will be hard to debug which attribute is not nil or what has failed. A better way to do this is to create a separate spec example for each attribute like
describe item, "Testing the Item" do
before(:each) do
#item = Item.new
end
propertyArray.each do |property|
it "should have a null value for #{property} to start" do
#item.send(property).should be_nil
end
end
end
This will run your spec as a different spec example for each property and if it fails then you will know what has failed. This also follows the rule of one assertion per test/spec example.
A couple points about Object#send()...
You can specify parameters for the method call too...
an_object.send(:a_method, 'A param', 'Another param')
I like to use this other form __send__ because "send" is so common...
an_object.__send__(:a_method)

Resources