1 rspec
Rspec is the second testing framework that is commonly used with Ruby on Rails projects. According to ruby-toolbox.com it is used more often than the (built in) Minitest.
Rspec replaces minitest in all aspects of rails, including in scaffolds.
The first things you hav to know to get started:
- tests are found in
spec/*
(nottest/*
) - to run one test use
rspec <filename>
orrspec <filename>:<linenumber>
on the command line - to run all tests use
rake spec
(yes, sometimes you need the r
in rspec, and other times you leave it out.)
1.1 A simple spec
A file can contain multiple test. You use describe
and it
to
structure the file. The arguments for describe
and it
are
used to describe the test in case of failure:
describe Game do
describe "#score" do
it "returns 0 for all gutter games" do
game = Game.new
20.times { game.roll(0) }
game.score.should == 0
end
end
end
The message when this test fails reads:
Failures:
1) Game#score returns 0 for all gutter games
Failure/Error: game.score.should == 0
expected: 0
got: 1 (using ==)
# ./x_spec.rb:15:in `block (3 levels) in <top (required)>'
It is a convention to actually use the Class under test as
the argument of describe
.
Inside the test you can use ruby and rails. Instead of minitest's assertions you formulate expectations with "should" (outdated) or "expect" (current):
game.score.should == 0
expect(game.score).to eq(0)
There are two ways of writing matchers:
foo.should == bar
foo.should eq(bar) expect(foo).to eq(bar)
foo.should_not eq(bar) expect(foo).not_to eq(bar)
foo.should be < 10 expect(foo).to be < 10
"a string".should_not =~ /a regex/
expect("a string").not_to match(/a regex/)
lambda { do_something }.should raise_error(SomeError)
expect { something }.to raise_error(SomeError)
1.2 Example Model Spec
describe Post do
context "with 2 or more comments" do
it "orders them in reverse chronologically" do
post = Post.create!
comment1 = post.comments.create!(:body => "first comment")
comment2 = post.comments.create!(:body => "second comment")
post.reload.comments.should == [comment2, comment1]
end
end
end
1.3 Example Feature Spec
feature "Widget management" do
scenario "User creates a new widget" do
visit "/widgets/new"
fill_in "Name", :with => "My Widget"
click_button "Create Widget"
page.should have_text("Widget was created.")
end
end
1.4 Kinds of Tests
- Model specs
- Controller specs
- View specs
- Helper specs
- Mailer specs
- Routing specs
- Request specs
- Feature specs
2.1 Step Definitions
the magic behind cucumber:
Given /the following movies exist/ do |movies_table|
movies_table.hashes.each do |movie|
Movie.create( movie )
end
end
Then /^the director of "([^"]*)" should be "([^"]*)"$/ do |title, director|
m = Movie.find_by_title( title )
m.should_not be_nil
m.director.should == director
end
3 Test Doubles
According to Meszaros(2007)
- Test stub provide canned answers to calls made during the test
- Mock object used for verifying "indirect output" of the tested code, by first defining the expectations before the tested code is executed
- Test spy used for verifying "indirect output" of the tested code, by asserting the expectations afterwards, without having defined the expectations before the tested code is executed
- Fake object used as a simpler implementation, e.g. using an in-memory database in the tests instead of doing real database access
5 Testing Time
describe "sets done_at" do
t = Todoitem.create!( :text => "write" )
t.done = true
t.save!
t.reload
t.done_at.should == Time.now
end
5.1 First solution: write your own matcher:
# in your test:
t.done_at.should be_the_same_time_as( Time.zone.now )
# in spec_helper.rb:
RSpec::Matchers.define :be_the_same_time_as do |expected|
match do |actual|
expected.to_i == actual.to_i
end
failure_message_for_should do |actual|
"expected that #{actual} (#{actual.to_i} in seconds) would be a the same as #{expected} (#{expected.to_i} in seconds)"
end
end
5.2 Second Solution: Timecop
it "sets done_at" do
t = Todoitem.create!( :text => "write" )
Timecop.freeze do
t.done = true
t.save!
t.reload
t.done_at.should == Time.now
end
end
6.1 Phantom Example
var page;
page = require("webpage").create();
page.open("http://localhost:3000", function(status) {
var string;
string = page.evaluate(function() {
return $("h1").text();
});
console.log("Title: " + string);
return phantom.exit();
});
var page;
page = require("webpage").create();
page.open("http://localhost:3000", function(status) {
var string;
string = page.evaluate(function() {
return $("h1").text();
});
console.log("Title: " + string);
return phantom.exit();
});