21. # lib/calc.rb
class Calc
def sum(a, b)
a + b
end
end
# spec/calc_spec.rb
require ‘spec_helper’
require ‘calc’
RSpec.describe Calc do
describe ‘#sum’ do
end
end
22.
23. Tip - use Describe strictly for class & methods
- describe “ClassName” do end
- describe “#instance_method” do end
- describe “.class_method” do end
24. describe tells who is tested
We need something to
Tell what we are testing of it
28. require ‘spec_helper’
require ‘calc’
RSpec.describe ‘Calc’ do
describe ‘#sum’ do
it “returns sum of 2 numbers” do
calc = Calc.new # setup
result = calc.sum(2, 3) # exercise
expect(result).to eql(5) # verify
end
end
end
29. require ‘calc’
RSpec.describe Calc do
describe ‘#sum’ do
it “returns sum of 2 numbers” do
calc = Calc.new # setup
expect(calc.sum(2, 3)).to eql(5) # exercise & verify
end
end
end
30. For brevity in the slides,
I’m gonna leave the require ‘spec_helper’
31. Tip - it statement
- don’t use “should” or “should not” in
description
- say about actual functionality, not what
might be happening
- Use only one expectation per example.
40. expect([1, 3, 5]).to all( be_odd )
# it is inclusive by default
expect(10).to be_between(5, 10)
# ...but you can make it exclusive: checks in range 4..9
expect(10).not_to be_between(5, 10).exclusive
# ...or explicitly label it inclusive:
expect(10).to be_between(5, 10).inclusive
42. Normal equality expectations do not work well for
floating point values
expect(27.5).to be_within(0.5).of(28.0)
expect(27.5).to be_within(0.5).of(27.2)
expect(27.5).not_to be_within(0.5).of(28.1)
expect(27.5).not_to be_within(0.5).of(26.9)
44. a_value > 3 be < 3
a_string_matching(/foo/) match(/foo/)
a_block_raising(ArgumentError) raise_error(ArgumentError)
See this gist for more aliases https://gist.github.com/JunichiIto/f603d3fbfcf99b914f86
Few matchers and their aliases
54. require ‘team’
RSpec.describe Team do
describe ‘#score’ do
it ‘increments goals’ do
team = Team.new
expect { team.score }.to change(team, :goals).by(1)
end
end
end
55. x = y = 0
expect {
x += 1
y += 2
}.to change { x }.to(1).and change { y }.to(2)
63. require ‘duh’
RSpec.describe ‘#duh’ do
it ‘says mumbo if number is odd’ do
expect(duh(3)).to eq “mumbo”
end
it ‘says jumbo if number is not odd’ do
expect(duh(4)).to eq “jumbo”
end
end
65. require ‘duh’
RSpec.describe ‘#duh’ do
context ‘when number is odd’ do
it ‘says mumbo’ do
expect(duh(3)).to eq “mumbo”
end
end
context ‘when number is not odd’ do
it ‘says jumbo’ do
expect(duh(4)).to eq “jumbo”
end
end
end
66. Tip - Context
- Always has an opposite negative
case
- So, never use a single context.
- Always begin with “when…”
68. require ‘team’
RSpec.describe Team do
describe ‘#score’ do
it ‘increments goals’ do
team = Team.new
expect(team.score).to change(Team.goals).by(1)
end
end
describe ‘#matches_won’ do
it ‘gives number of matches won by the team” do
team = Team.new
expect(team.matches_won).to eq 0
end
end
end
69. require ‘team’
RSpec.describe Team do
let(:team) { Team.new }
describe ‘#score’ do
it ‘increments goals’ do
expect(team.score).to change(Team.goals).by(1)
end
end
describe ‘#matches_won’ do
it ‘gives number of watches won by the team” do
expect(team.matches_won).to eq 0
end
end
end
72. require ‘team.’
RSpec.describe Team do
before do
@team = Team.new
puts “Called every time before the it or specify block”
end
describe ‘#score’ do
it ‘increments goals of the match’ do
expect(@team.score).to change(Team.goals).by(1)
end
it ‘increments total goals of the Team’’ do
expect(@team.score).to change(Team.total_goals).by(1)
end
end
end
73. require ‘team’
RSpec.describe Team do
before(:suite) do
puts “Get ready folks! Testing are coming!! :D ”
end
describe ‘#score’ do
it ‘increments goals of the match’ do
expect(@team.score).to change(Team.goals).by(1)
end
it ‘increments total goals of the Team’’ do
expect(@team.score).to change(Team.total_goals).by(1)
end
end
end
74. Types passed to before/after
- :example (runs for each test)
- :context (runs for each context)
- :suite (runs for entire suite, only
once, see database cleaner gem)
75. Tip - Use let instead of before
- To create data for the spec
examples.
- let blocks get lazily evaluated
76. # use this:
let(:article) { FactoryGirl.create(:article) }
# ... instead of this:
before { @article = FactoryGirl.create(:article) }
77. Tip: Use before/after for
- actions or
- when the same obj/variable needs to be
used in different examples
78. before do
@book = Book.new(title: "RSpec Intro")
@customer = Customer.new
@order = Order.new(@customer, @book)
@order.submit
end
81. class PriceCalculator
def add(product)
products << product
end
def products
@products ||= []
end
def total
@products.map(&:price).inject(&:+)
end
end
class Product
end
describe PriceCalculator do
it "allows for method stubbing" do
calculator = PriceCalculator.new
calculator.add(double(price: 25.4))
calculator.add(double(price: 101))
expect(calculator.total).to eq 126.4
end
end
#This works even if there is no Product class is defined #
in the actual program
82. class Product
attr_reader :price
end
class PriceCalculator
def add(product)
products << product
end
def products
@products ||= []
end
def total
@products.map(&:price).inject(&:+)
end
end
describe PriceCalculator do
it "allows for method stubbing" do
calculator = PriceCalculator.new
calculator.add instance_double("Product", price: 25.4)
calculator.add instance_double("Product", price: 101)
expect(calculator.total).to eq 126.4
end
end
# throws and error if a Product class or its methods are #
not defined
83. $ irb
> require ‘rspec/mocks/standalone’
> class User; end
> allow(User).to receive(:wow).and_return(“Yolo”)
> User.wow
=> “Yolo Yolo”
84. You can also use block to return instead
and_return
allow(User).to receive(:wow) { (“Yolo”) }
85. 3 types of return for wow method
allow(User).to receive(:wow)
.and_return(“yolo”, “lol”, “3rd time”)
86. Diff output when running diff times
User.wow #=> yolo
User.wow #=> lol
User.wow #=> 3rd time
User.wow #=> 3rd time
User.wow #=> 3rd time
87. So, you could use it as if it is 2 different objects
2.times { calculator.add product_stub }
88. Diff between double & instance_double
Instance double requires
- class to be defined
- methods to be defined in that class.
- Only then a method can be allowed to it.
102. RSpec.describe FacebookAPI do
it "has posts" do
expect(FbAPI.new("vysakh0")).to respond_to :posts
end
it_behaves_like("API", FbAPI.new(“vysakh0”))
end
Rspec.describe TwitterAPI do
it "has tweets" do
expect(TwitterAPI.new("vysakh0")).to respond_to :tweets
end
it_behaves_like("API", TwitterAPI.new(“vysakh0”))
end
103. RSpec.shared_examples_for "API" do |api|
it "returns a formatted hash" do
expect(api.profile).to match [
a_hash_including(
name: an_instance_of(String),
category: an_instance_of(String),
price: an_instance_of(Float))
]
end
end
105. RSpec.shared_context "shared stuff" do
before { @some_var = :some_value }
def shared_method
"it works"
end
let(:shared_let) { {'arbitrary' => 'object'} }
subject do
'this is the subject'
end
end
110. RSpec::Matchers.define :be_bigger_than do |min|
chain :but_smaller_than, :max
match do |value|
value > min && value < max
end
end
# usage:
expect(10).to be_bigger_than(5).but_smaller_than(15)