$30 off During Our Annual Pro Sale. View Details »

RSpec for Practical Rubyist

RSpec for Practical Rubyist

@ RubyConfTaiwan 2015

Juanito Fatas

September 11, 2015
Tweet

More Decks by Juanito Fatas

Other Decks in How-to & DIY

Transcript

  1. RSPEC
    FOR THE
    PRACTICAL RUBYIST
    EXPLAINING SIMPLY AND QUICKLY
    ALL THE ELEMENTS OF
    CONTROLLER, MODEL, VIEW,
    HELPER, INTEGRATION, FEATURE
    WITHout
    ANSWERS TO PROBLEMS

    View Slide

  2. 2015!!!
    RubyConFTW

    View Slide

  3. rubytaiwan/taipei
    Things to know about Taipei put together by
    Ruby Taiwan Community
    Inspired from RubySG/Singapore

    View Slide

  4. Juanito Fatas
    !

    View Slide

  5. juanitofatas/fast-ruby
    Collection of Idioms, Benchmarks, etc. for Faster Ruby Code
    Inspired by @sferik and Ruby Community

    View Slide

  6. Employee #1

    View Slide

  7. The Birth of deppbot

    View Slide

  8. JuanitoFatas/rubygems.org/pull/1

    View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. JOIN
    OPEN
    SOURCE

    View Slide

  13. Tweet at Me
    @juanitofatas
    "
    #rubyconFTW
    #rubyconFTW2015

    View Slide

  14. View Slide

  15. Introducing
    4K
    Live Photos
    iPhone 6S Plus

    View Slide

  16. muan/mojibar

    View Slide

  17. Emoji is
    Awesome!

    View Slide

  18. 95% of time
    preparations
    of this talk

    View Slide

  19. Tweak fonts and
    think about emojis

    View Slide

  20. First of all

    View Slide

  21. It’s been a
    tough day

    View Slide

  22. %

    View Slide

  23. View Slide

  24. Some
    mathematics

    View Slide

  25. My first talk ever,
    5-minute long ⏳

    View Slide

  26. My second talk,
    50-minute long

    View Slide

  27. Let ψ = 5

    View Slide

  28. Let ω = 50

    View Slide

  29. ω

    ψ
    =

    View Slide

  30. 50

    ψ
    =

    View Slide

  31. 50

    5
    =

    View Slide

  32. 50

    5
    =10

    View Slide


  33. View Slide

  34. I am a
    10x
    Speaker

    View Slide

  35. Q.E.D.

    View Slide

  36. Matz is
    Great!
    ,,,,,,

    View Slide

  37. Talk Title
    Origin

    View Slide

  38. Richard Feynman

    View Slide

  39. View Slide

  40. RSPEC
    FOR THE
    PRACTICAL RUBYIST
    EXPLAINING SIMPLY AND QUICKLY
    ALL THE ELEMENTS OF
    CONTROLLER, MODEL, VIEW,
    HELPER, INTEGRATION, FEATURE
    WITHout
    ANSWERS TO PROBLEMS

    View Slide

  41. Why This Talk

    View Slide

  42. Rubyists in Taiwan
    are courageous.

    View Slide

  43. View Slide

  44. Raise the awareness
    of Testing in Thiland

    View Slide

  45. Journey into
    Rails Testing
    ⛵0

    View Slide

  46. Introductory Talk
    No theory, and TDD because
    How-to, but not what & why
    Tutorial type




    View Slide

  47. Just Enough
    Background

    View Slide

  48. correctness
    quality
    buying time for the future
    finding regressions
    save repeated labor
    gives you confidence
    easy refactoring
    perfect bug report
    live documentation
    drive your implementation
    automation is win





    View Slide

  49. The
    Rabbit Hole

    View Slide

  50. What is RSpec
    "

    View Slide

  51. View Slide

  52. https://relishapp.com/rspec
    RSpec Core | RSpec Expectations | RSpec Mocks | RSpec Rails

    View Slide

  53. RSpec
    Minitest
    vs

    View Slide

  54. A professional developer should
    be able to work in either one of
    these because they essentially do
    the same thing: test your code.
    — Dr.Tenderlove

    http://tenderlovemaking.com/2015/01/23/my-experience-with-minitest-and-rspec.html

    View Slide

  55. class Something < Minitest::Test
    def test_works
    assert_equal 11, 10
    end
    def test_really_works
    assert_equal 11, 11
    end
    end
    (Unit)Test

    View Slide

  56. Test cases are
    Class Minitest::Test
    Tests are
    methods inside test case

    View Slide

  57. describe "something" do
    it "works" do
    expect(11).to equal(10)
    end
    it "really works" do
    expect(11).to equal(11)
    end
    end
    Spec(ification)

    View Slide

  58. Test cases are describe
    with description string / actual object
    and a block of tests
    Tests are it
    with description string and
    the body of test (block)

    View Slide

  59. View Slide

  60. RSpec
    RAILS

    View Slide

  61. A Gentle Introduction
    to RSpec

    View Slide

  62. rspec
    rspec-core
    rspec-expectations
    rspec-mocks
    MetaRubyGem



    View Slide

  63. How to run

    View Slide

  64. . means Passed
    F means Failure
    E means Error
    * means Pending
    S means Skip

    View Slide

  65. Sample Output

    View Slide

  66. View Slide

  67. $ rspec --init

    View Slide

  68. .rspec
    --color
    --require spec_helper
    https://relishapp.com/rspec/rspec-core/v/3-3/docs/command-line/

    View Slide

  69. spec/
    spec_helper.rb

    View Slide

  70. Magic Spells

    View Slide

  71. Computer science is a terrible name for this
    business. First of all, it's not a science. It
    might be engineering or it might be art, but
    we'll actually see that computer so-called
    science actually has a lot in common with
    magic.— Hal Abelson

    https://youtu.be/2Op3QLzMgSY?t=27s

    View Slide

  72. describe
    it
    expect
    to

    View Slide

  73. describe
    it
    expect
    to
    a series of tests

    View Slide

  74. describe
    it
    expect
    to
    a series of tests
    is a test

    View Slide

  75. describe
    it
    expect
    to
    a series of tests
    is a test
    an actual object

    View Slide

  76. describe
    it
    expect
    to
    a series of tests
    is a test
    an actual object
    match something

    View Slide

  77. describe
    how methods works,
    group of test examples,
    series of behaviour

    View Slide

  78. describe User do
    ...
    end

    View Slide

  79. RSpec.describe
    describe
    config.disable_monkey_patching!
    in spec_helper.rb
    34QFD

    View Slide

  80. RSpec.describe User do
    describe ".authenticate" do
    ...
    end
    describe "#name" do
    ...
    end
    end describe can be nested

    View Slide

  81. RSpec.describe User do
    describe ".authenticate" do
    ...
    end
    describe "#name" do
    ...
    end
    end

    View Slide

  82. RSpec.describe User do
    describe ".authenticate" do
    ...
    end
    describe "#name" do
    ...
    end
    end

    View Slide

  83. RSpec.describe User do
    describe ".authenticate" do
    ...
    end
    describe "#name" do
    ...
    end
    end

    View Slide

  84. RSpec.describe User do
    describe ".authenticate" do
    ...
    end
    describe "#name" do
    ...
    end
    end

    View Slide

  85. RSpec.describe User do
    describe ".authenticate" do
    ...
    end
    describe "#name" do
    ...
    end
    end

    View Slide

  86. context
    scenarios in your
    implementation
    same as describe

    View Slide

  87. context "when…" do
    ...
    end
    context "with…" do
    end

    View Slide

  88. it
    a single test example
    inside of
    describe / context block

    View Slide

  89. it "eats something" do
    ...
    end

    View Slide

  90. describe SteveJobs do
    context "with stylus" do
    it "yucks" do
    end
    end
    end

    View Slide

  91. $ bin/rspec spec/models/steve_jobs_spec.rb --format doc
    SteveJobs
    with stylus
    yucks

    View Slide

  92. it "hello" do

    end
    4JOHMF-JOF
    .VMUJ-JOF
    it { … }

    View Slide

  93. example "" do
    ...
    end
    "WPJE
    specify "" do
    ...
    end

    View Slide

  94. before
    things to do before
    test run

    View Slide

  95. before { ... }
    it "" do
    ...
    end

    View Slide

  96. before do
    ...
    end
    it "" do
    ...
    end

    View Slide

  97. after
    things to do after
    test run

    View Slide

  98. it "" do
    ...
    end
    after { ... }

    View Slide

  99. it "" do
    ...
    end
    after do

    end

    View Slide

  100. inside of it

    View Slide

  101. Expectations &
    matchers come in

    View Slide

  102. expect(actual)
    expect something

    View Slide

  103. to eq(expected)
    to equal what we
    expected

    View Slide

  104. it "…" do
    expect(1+1).to eq(2)
    end
    from rspec-expectations

    View Slide

  105. it "…" do
    expect(1+1).to eq(2)
    end
    matcher

    View Slide

  106. expect(1+1).to eq(2)

    View Slide

  107. 1+1 is 2?
    Let’s prove

    View Slide

  108. 162 pages
    to prove
    1+1=2

    View Slide

  109. it "…" do
    expect(1+1).to eq(2)
    end
    Example instance
    http://git.io/vZfvX

    View Slide

  110. it "…" do
    self.expect(1+1).to self.eq(2)
    end
    Example Example

    View Slide

  111. it "…" do
    self.expect(1+1).to self.eq(2)
    end
    instance of
    ExpectationTarget
    http://git.io/vZffB

    View Slide

  112. ET = ExpectationTarget
    class Example
    def self.expect(actual)
    ET.new(actual)
    end
    end

    View Slide

  113. ET = ExpectationTarget
    class Example
    def self.expect(actual)
    ET.new(actual)
    end
    end

    View Slide

  114. ET = ExpectationTarget
    class Example
    def self.expect(actual)
    ET.new(actual)
    end
    end

    View Slide

  115. class ET
    attr_reader :actual
    def initialize(actual)
    @actual = actual
    end
    end

    View Slide

  116. it "…" do
    self.expect(1+1).to self.eq(2)
    end
    Example

    View Slide

  117. it "…" do
    self.expect(1+1).to self.eq(2)
    end
    Matcher

    View Slide

  118. class Example
    def self.eq(expected)
    EqMatcher.new(expected)
    end
    end
    EqMatcher is actually BuiltIn::Eq
    http://git.io/vZffi

    View Slide

  119. class EqMatcher
    def initialize(expected)
    @expected = expected
    end
    def matches
    ...
    end
    end

    View Slide

  120. it "…" do
    self.expect(1+1).to self.eq(2)
    end
    instance of
    ExpectationTarget

    View Slide

  121. it "…" do
    self.expect(1+1).to self.eq(2)
    end
    ExpectationTarget#to

    View Slide

  122. class ExpectationTarget
    def to(matcher)
    Postive.handle_matcher(actual, matcher)
    end
    end

    View Slide

  123. class ExpectationTarget
    def to(matcher)
    Postive.handle_matcher(actual, matcher)
    end
    end
    Postive is actually PositiveExpectationHandler
    http://git.io/vZfJK

    View Slide

  124. class Positive
    def self.handle_matcher(actual, matcher)
    matcher.matches(actual)
    end
    end
    http://git.io/vZqoX

    View Slide

  125. class Positive
    def self.handle_matcher(actual, matcher)
    matcher.matches(actual)
    end
    end

    View Slide

  126. class EqMatcher
    def matches(actual)
    match(expected, actual)
    end
    private
    def match(expected, actual)
    actual == expected
    end
    end

    View Slide

  127. class EqMatcher
    def matches(actual)
    match(expected, actual)
    end
    private
    def match(expected, actual)
    actual == expected
    end
    end

    View Slide

  128. class EqMatcher
    def matches(actual)
    match(expected, actual)
    end
    private
    def match(expected, actual)
    actual == expected
    end
    end
    http://git.io/vZfUp

    View Slide

  129. voilà, old sport!

    View Slide

  130. it "…" do
    expect(user.admin?).to be_true
    end
    `be` matcher
    be_true
    be_false
    be_truthy
    be_falsy
    be_nil
    be

    View Slide

  131. eq
    eql
    equal
    be
    be_*
    match
    be_within
    start_with
    end_with
    be_instance_of
    be_kind_of
    respond_to
    raise_error
    throw_symbol
    include
    match_array
    contain_exactly
    cover
    change
    satisfy
    output
    yield_*
    https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers

    "1*

    View Slide

  132. expect { code }.to raise_error(error, message)
    expect { post :create }
    .to raise_error(
    ActiveRecord::RecordInvalid,
    "Validation failed: name can't be blank"
    )
    raise_error matcher

    View Slide

  133. expect { code }.to change { something }.by(value)
    def do_request
    post :create, user: params
    end
    expect { do_request }.to change { User.count }.by(1)
    change matcher

    View Slide

  134. RSpec predicate
    expect(user.admin?).to be_true
    expect(user).to be_admin

    View Slide

  135. Read in English
    Most of the time
    There is a
    matcher for it
    (
    VJEF

    View Slide

  136. Stick with eq
    as a Starting Point
    (
    VJEF
    KISS

    View Slide

  137. expect(user.admin?).to be_true
    expect(user.admin?).to eq true

    View Slide

  138. RSpec built-in
    matchers have
    better error messages
    (
    VJEF

    View Slide

  139. let
    lazily-evaluated block
    to shared object between tests

    View Slide

  140. describe Baz
    let(:foo) { "bar" }
    it "…" do
    ...
    end
    end
    foo will be available in it
    blocks under the same scope!

    View Slide

  141. let(:foo) { sleep 42 }
    it "…" do
    ...
    end
    Finished in 0.04424 seconds
    it "…" do
    ...
    end

    View Slide

  142. let(:foo) { sleep 42 }
    it "…" do
    foo
    end
    Finished in 42.03924 seconds
    it "…" do
    ...
    end

    View Slide

  143. let!(:foo) { sleep 42 }
    it "…" do
    ...
    end
    Finished in 42.03924 seconds
    it "…" do
    ...
    end

    View Slide

  144. Prefer let over
    @instance_var
    (
    VJEF

    View Slide

  145. RSpec Mocks

    View Slide

  146. RSpec Mocks
    To mock a mocking bird

    View Slide

  147. We are dealing with
    objects and objects

    View Slide

  148. collaborators of
    each other

    View Slide

  149. Sometimes
    it costs a lot to setup
    collaborators

    View Slide

  150. TDD
    Tired-Driven Development

    View Slide

  151. Move Fast and
    Break Things
    drive slow but stay safe

    View Slide

  152. Double

    View Slide

  153. View Slide

  154. A fake object for
    real object

    View Slide

  155. RSpec::Mocks::
    ExampleMethods
    #double
    http://git.io/vZq6F
    "1*

    View Slide

  156. user = double("User")

    View Slide

  157. Faking message and
    its return value

    View Slide

  158. RSpec::Mocks::
    ExampleMethods
    #allow
    http://git.io/vZq6F
    "1*

    View Slide

  159. allow(obj).to receive(:msg).and_return(value)

    View Slide

  160. user = double("User")
    allow(user).to
    receive(:admin?).
    and_return(true)
    allow(user).to receive(:admin?) { true }

    View Slide

  161. user = double("User")
    allow(user).to
    receive(:admin?).
    and_return(true)
    allow(user).to receive(:admin?) { true }

    View Slide

  162. user = double("User")
    allow(user).to
    receive(:admin?).
    and_return(true)
    allow(user).to receive(:admin?) { true }

    View Slide

  163. user.admin?
    => true

    View Slide

  164. user =
    double("User", admin?: true)

    View Slide

  165. Stub & Mock

    View Slide

  166. context "stub and mock" do
    describe "stub" do
    it "won't verify" do
    allow(User).to receive(:new)
    # User.new
    end
    end
    describe "mock" do
    it "will verify" do
    expect(User).to receive(:new)
    # User.new
    end
    end
    end

    View Slide

  167. context "stub and mock" do
    describe "stub" do
    it "won't verify" do
    allow(User).to receive(:new)
    # User.new
    end
    end
    describe "mock" do
    it "will verify" do
    expect(User).to receive(:new)
    # User.new
    end
    end
    end
    Stub

    View Slide

  168. context "stub and mock" do
    describe "stub" do
    it "won't verify" do
    allow(User).to receive(:new)
    # User.new
    end
    end
    describe "mock" do
    it "will verify" do
    expect(User).to receive(:new)
    # User.new
    end
    end
    end
    Mock

    View Slide

  169. context "stub and mock" do
    describe "stub" do
    it "won't verify" do
    allow(User).to receive(:new)
    # User.new
    end
    end
    describe "mock" do
    it "will verify" do
    expect(User).to receive(:new)
    # User.new
    end
    end
    end
    Failure/Error: expect(User).to receive(:new)

    Mock

    View Slide

  170. Mock will verify
    While Stub not

    View Slide

  171. context "stub and mock" do
    describe "stub" do
    it "stub and verify" do
    allow(User).to receive(:new)
    User.new
    expect(User).to have_received(:new)
    end
    end
    end

    View Slide

  172. context "stub and mock" do
    describe "stub" do
    it "won't verify" do
    allow(User).to receive(:new)
    User.new
    expect(User).to have_received(:new)
    end
    end
    end
    Strategy Spaces

    View Slide

  173. Phases of Testing

    View Slide

  174. setup
    exercise
    verify
    teardown
    everything we need
    the real work
    our expectations
    back to original state

    View Slide

  175. Encrypt user password
    in database
    &YBN
    QMF

    View Slide

  176. setup
    user =
    User.new(password: "passw0rd")

    View Slide

  177. user.save
    exercise

    View Slide

  178. expect(user.encrypted_password).to be_present
    verify

    View Slide

  179. RSpec’ve got us covered
    teardown

    View Slide

  180. before { user = User.new(password: "passw0rd") }
    it "should encrypt password" do
    user.save
    expect(user.encrypted_password).not_to eq nil
    end
    Put together

    View Slide

  181. it "should encrypt password" do
    user = User.new(password: "passw0rd")
    user.save
    expect(user.encrypted_password).not_to eq nil
    end
    Strategy Spaces

    View Slide

  182. Spy

    View Slide

  183. Like 007,
    can do
    anything

    View Slide

  184. Can
    respond
    to any
    messages

    View Slide

  185. oo7 = spy
    oo7.can_jump?
    oo7.can_drive_car?
    oo7.can_play_poker?
    oo7.can_write_ruby?

    View Slide

  186. spy is the Null
    Object of double
    spy = double.as_null_object

    View Slide

  187. oo7 = spy
    oo7.hungry?
    expect(oo7).to have_received(:hungry?)

    View Slide

  188. oo7 = spy
    oo7.tired?
    expect(oo7).to have_received(:tired?)
    Just like Stub!

    View Slide

  189. RSpec 2 v.s RSpec 3
    make stub and mock
    is confusing

    View Slide

  190. But you can
    just try and
    explore their
    difference

    View Slide

  191. That’s enough to
    write many tests!

    View Slide

  192. RSpec & Rails

    View Slide

  193. gem "rspec-rails"
    bundle
    rails generate rspec:install
    *OTUBMM
    in development and test group

    View Slide

  194. spec/
    rails_helper.rb
    Only when you need Rails

    View Slide

  195. Try not to load
    rails_helper
    whenever possible
    5JQ

    View Slide

  196. config.infer_spec_type_from_file_location!
    # spec/models/user_spec.rb
    RSpec.describe User, type: :model do
    # automatically mixin functionalities
    end
    Infer type of test automatically

    View Slide

  197. Metadata

    View Slide

  198. RSpec.describe User, type: :model do
    end
    metadata

    View Slide

  199. According to type of tests,
    RSpec will mixin
    different functionality
    automagically

    View Slide

  200. RSpec.describe User, focus: true do
    end
    metadata
    $ rspec --tag focus
    only run spec with special tag

    View Slide

  201. Rails & Testing

    View Slide

  202. RAILS_ENV = test
    config/environments/test.rb

    View Slide

  203. bin/rake db:test:prepare
    run pending migrations and load schema
    https://github.com/rails/rails/blob/v4.2.4/activerecord/lib/active_record/railties/databases.rake#L359-L364

    View Slide

  204. ActiveSupport::TestCase
    ActionController::TestCase
    ActionMailer::TestCase
    ActionView::TestCase
    ActiveJob::TestCase
    ActionDispatch::IntegrationTest
    Read these API docs on
    http://api.rubyonrails.org/

    View Slide

  205. Fixture

    View Slide

  206. Fixture
    # spec/fixtures/users.yml
    juanitofatas:
    name: JuanitoFatas
    email: [email protected]
    # spec/rails_helper.rb
    RSpec.configure do |config|
    config.fixture_path = "#{::Rails.root}/spec/fixtures"
    config.use_transactional_fixtures = true
    end
    # under describe or context
    users :juanitofatas
    # => #

    View Slide

  207. FactoryGirl
    A DSL to generate objects

    View Slide

  208. # spec/factories/user.rb
    Factory.define do
    factory :user do
    name "Juanito Fatas"
    email "[email protected]"
    end
    end

    View Slide

  209. FactoryGirl.attributes_for(:user)
    # => {
    name: "JuanitoFatas",
    email: "[email protected]"
    }

    View Slide

  210. # In spec
    FactoryGirl.create(:user)
    FactoryGirl.build(:user)
    # Map to Active Record
    User.create(…)
    User.new(…)

    View Slide

  211. gem "factory_girl_rails"
    bundle
    mkdir spec/factories
    *OTUBMM
    in development and test group

    View Slide

  212. spec/factories/*.rb
    $POWFOUJPO
    * is respective model name, user.rb

    View Slide

  213. RSpec.configure do |config|
    config.include FactoryGirl::Syntax::Methods
    end
    $PO
    H
    create(:user) instead of
    FactoryGirl.create(:user)
    https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md

    View Slide

  214. Minimum
    Valid
    Object
    5JQ

    View Slide

  215. Types of Test

    View Slide

  216. https://robots.thoughtbot.com/rails-test-types-and-the-testing-pyramid

    View Slide

  217. https://robots.thoughtbot.com/rails-test-types-and-the-testing-pyramid
    Bottom
    Top

    View Slide

  218. https://robots.thoughtbot.com/rails-test-types-and-the-testing-pyramid
    Inside Outside

    View Slide

  219. https://robots.thoughtbot.com/rails-test-types-and-the-testing-pyramid
    User
    Code

    View Slide

  220. https://robots.thoughtbot.com/rails-test-types-and-the-testing-pyramid
    Coverage

    View Slide

  221. https://robots.thoughtbot.com/rails-test-types-and-the-testing-pyramid
    Fast Slow

    View Slide

  222. View Slide

  223. Feature Spec
    Outside-in, Acceptance / Customer Test, End-to-End, Top down

    View Slide

  224. require "rails_helper"
    spec/features/*_spec.rb
    $POWFOUJPO

    View Slide

  225. (
    PBM
    Automate the manual
    repeated process
    Browser, check, refresh, repeat

    View Slide

  226. Test like how user use it
    Use real database records
    Don’t mock or stub objects
    External Service can stub
    5JQ

    View Slide

  227. Happy Path for
    a given feature
    5JQ

    View Slide

  228. API to interact
    with Browser
    (
    FN
    T
    capybara
    Version 2.4.0+ is recommended

    View Slide

  229. group :development, :test do
    gem "capybara"
    end
    bundle install
    *OTUBMM

    View Slide

  230. require "capybara/rails"
    # in spec/rails_helper.rb
    $PO
    H

    View Slide

  231. database_rewinder
    (
    FN
    T
    Ensure clean
    state across tests

    View Slide

  232. group :test do
    gem "database_rewinder"
    end
    *OTUBMM

    View Slide

  233. RSpec.configure do |config|
    config.before :suite do
    DatabaseRewinder.clean_all
    end
    config.after :each do
    DatabaseRewinder.clean
    end
    end
    $PO
    H

    View Slide

  234. background
    feature
    scenario
    given
    "1*

    View Slide

  235. &YBN
    QMF
    feature "Auth" do
    given { ... }
    background { ... }
    scenario "show name" do
    ...
    end
    end

    View Slide

  236. background
    feature
    scenario
    given
    before
    describe
    it
    let
    http://git.io/vGyaV
    alias
    alias
    alias
    alias
    "1*

    View Slide

  237. RSpec.feature "…" do
    end
    Capybara API will be
    available in here
    http://www.rubydoc.info/github/jnicklas/capybara/master#The_DSL

    View Slide

  238. "1*
    page
    visit
    current_path
    click_on
    find
    fill_in
    choose
    check
    select
    attach_file
    https://github.com/jnicklas/capybara#the-dsl

    View Slide

  239. 3BDL5FTU
    Fastest
    No JavaScript
    Can access within Rack
    No Server Required

    View Slide

  240. &YBN
    QMF
    feature "Sign In" do
    scenario "works" do
    visit root_path
    click_on "Sign In"
    fill_in "login", with: "JuanitoFatas"
    fill_in "password", with: "1passw0rd"
    click_on "Sign In"
    end
    end

    View Slide

  241. &YBN
    QMF
    feature "Auth" do
    scenario "show name" do
    create(:user, name: "Juan")
    sign_in_as(user)
    expect(page).to have_content("Juan")
    end
    end

    View Slide

  242. &YBN
    QMF
    feature "Vote" do
    scenario "upvote" do
    create(:user, name: "Juan")
    sign_in_as(user)
    expect(page).to have_content("Votes 0")
    find("upvote").click
    expect(page).to have_content("Votes 1")
    end
    end

    View Slide

  243. Drivers

    View Slide

  244. %
    SJWFST
    Plain
    Firefox
    Webkit
    PhantomJS
    Rack-Test
    Selenium
    Capybara-webkit
    Poltergeist
    Single DSL to rule them all

    View Slide

  245. RSpec.describe User, js: true do
    ...
    end
    enable javascript_driver
    #SPX
    TFS

    View Slide

  246. 4FMFOJVN
    Selenium 2.0
    Firefox
    JavaScript
    Can access Remote API
    EFGBVMU

    View Slide

  247. $BQZCBSB8FCLJU
    Headless Testing
    JavaScript
    Faster than selenium

    View Slide

  248. 1PMUFSHFJTU
    Headless Testing
    JavaScript
    Can Find JS Error
    No render engine needed

    View Slide

  249. Capybara.default_driver
    Capybara.javascript_driver
    $PO
    H

    View Slide

  250. Controller Spec
    Functional Test

    View Slide

  251. require "rails_helper"
    spec/controllers/*_spec.rb
    describe "#action"
    $POWFOUJPO
    * is respective controller name

    View Slide

  252. require "rails_helper"
    RSpec.describe ShopsController do
    describe "#index" do
    ...
    end
    describe "#new" do
    ...
    end
    describe "#create" do
    ...
    end

    $POWFOUJPO

    View Slide

  253. describe "#show" do
    ...
    end
    describe "#edit" do
    ...
    end
    describe "#update" do
    ...
    end
    describe "#destroy" do
    ...
    end
    end
    $POWFOUJPO

    View Slide

  254. 5JQ
    Ordering should be the same
    as your actions in controller
    index new create show edit update destroy

    View Slide

  255. response correct & success
    redirected right
    authentications
    session, flash, cookie
    render correct template
    instance variable
    (
    PBM

    View Slide

  256. params
    request
    response
    session
    flash
    cookie
    "1*
    get
    post
    patch
    put
    head
    delete
    ActionController::TestCase

    View Slide

  257. expect(response).to render_template(:index)
    expect(response).to redirect_to(location)
    expect(response.status).to eq(200)
    expect(flash[:alert]).to eq I18n.t("failed")
    "1*

    View Slide

  258. Can wrap request in
    method — abstractions
    5JQ
    def do_request
    get :show, params
    end

    View Slide

  259. RSpec.describe HomeController do
    describe "#index" do
    def do_request
    get :index
    end
    it "works" do
    do_request
    expect(response).to be_success
    end
    end
    end
    JOEFY

    View Slide

  260. describe HomeController do
    describe "#index" do
    def do_request
    get :index
    end
    it "works" do
    do_request
    expect(response).to be_success
    end
    end
    end
    &YBN
    QMF
    same scope

    View Slide

  261. RESTful
    controller spec

    View Slide

  262. RSpec.describe ThingsController do
    end
    TLFMUPO

    View Slide

  263. describe "#index" do
    def do_request
    get :index
    end
    before { do_request }
    it { expect(response).to be_success }
    end
    JOEFY

    View Slide

  264. describe "#new" do
    def do_request
    get :new
    end
    before { do_request }
    it { expect(response).to be_success }
    end
    OFX

    View Slide

  265. describe "#create" do
    def do_request
    post :create, thing: params
    end
    context "success" do
    ...
    end
    context "failure" do
    ...
    end
    end
    DSFBUF

    View Slide

  266. describe "#show" do
    let(:thing) { create(:thing) }
    def do_request
    get :show, id: thing
    end
    before { do_request }
    it { expect(response).to be_success }
    end
    TIPX

    View Slide

  267. FEJU
    describe "#edit" do
    let(:thing) { create(:thing) }
    def do_request
    get :edit, id: thing.id
    end
    before { do_request }
    it { expect(response).to be_success }
    end

    View Slide

  268. describe "#update" do
    let(:thing) { create(:thing) }
    def do_request
    patch :update, id: thing.id, thing: params
    end
    before { do_request }
    context "success" do
    ...
    end
    context "failure" do
    ...
    end
    end
    VQEBUF

    View Slide

  269. describe "#destroy" do
    let!(:thing) { create(:thing) }
    def do_request
    delete :destroy, id: thing.id
    end
    it "redirects" do
    expect { do_request }.to change(Thing, :count).by(-1)
    expect(response).to redirect_to things_path
    end
    end
    EFTUSPZ

    View Slide

  270. 5JQ
    Try to keep the specs shape
    the same across controllers

    View Slide

  271. 3BJMT
    post :show,
    { id: 1 },
    { user_id: 1 },
    { notice: "flash message" }

    View Slide

  272. 3BJMT
    post :show,
    { id: 1 },
    {},
    { notice: "flash message" }
    params
    session
    flash

    View Slide

  273. 3BJMT
    post :show,
    { shop_id: 1 },
    {},
    { notice: "flash message" }
    params
    session
    flash

    View Slide

  274. 3BJMT
    post :show,
    params: { shop_id: 1 },
    session: { user_id: 1 },
    flash: { notice: "flash message" }
    Rails 5 Switch to Keyword Arguments
    https://github.com/rails/rails/pull/18323

    View Slide

  275. 5JQ
    Wrap things in Objects,
    only test message has been
    invoked in controller
    skinny controller

    View Slide

  276. describe "#create" do
    context "success" do
    it "sent welcome mail" do
    allow(UserMailer).to receive(:welcome)
    do_request
    expect(UserMailer).to have_received(:welcome)
    end
    end
    end
    &YBN
    QMF

    View Slide

  277. describe "#create" do
    context "success" do
    it "sent welcome mail" do
    allow(Payment).to receive(:charge!)
    do_request
    expect(Payment).to have_received(:charge!)
    end
    end
    end

    View Slide

  278. Model Spec

    View Slide

  279. Test how you tried
    in rails console
    (
    PBM

    View Slide

  280. require "rails_helper"
    spec/models/*_spec.rb
    $POWFOUJPO

    View Slide

  281. describe User do
    context "associations" do; end
    context "validations" do; end
    describe ".class_method" do; end
    describe "#instance_method" do; end
    end
    $POWFOUJPO

    View Slide

  282. Use shoulda
    to write one-liner spec
    for associations and
    validations
    5JQ

    View Slide

  283. ActiveSupport::TestCase
    3BJMT

    View Slide

  284. describe User do
    context "associations" do
    it { expect(User.new).to have_many(:repos) }
    end
    end
    "TTPDJBUJPO

    View Slide

  285. describe User do
    subject { User.new }
    context "associations" do
    it { expect(subject).to have_many(:repos) }
    end
    end
    &YBN
    QMF
    Implicit subject

    View Slide

  286. describe User do
    context "associations" do
    it { is_expected.to have_many(:repos) }
    end
    end
    &YBN
    QMF

    View Slide

  287. describe User do
    context "associations" do
    it { is_expected.to validate_presence_of :name }
    end
    end
    7BMJEBUJPO

    View Slide

  288. Post.trending
    scope :trending,
    -> { order("upvotes DESC") }
    describe Post do
    describe ".trending" do
    popular = create(:post, upvotes: 42)
    just_created = create(:post, upvotes: 1)
    trending_posts = Post.trending
    expect(trending_posts).to \
    eq [popular, just_created]
    end
    end

    View Slide

  289. Helper Spec

    View Slide

  290. (
    PBM
    Test the view helper as
    you expected

    View Slide

  291. require "rails_helper"
    spec/helpers/*_spec.rb
    $POWFOUJPO

    View Slide

  292. require "rails_helper"
    describe EventsHelper do
    describe "#linkify_event" do
    it "works" do
    expect(linkify_event).to eq "RubyKaigi 11st-13rd Dec, 2015"
    end
    end
    end
    &YBN
    QMF

    View Slide

  293. require "rails_helper"
    describe EventsHelper do
    describe "#linkify_event" do
    it "works" do
    expect(linkify_event).to eq "RubyKaigi 11st-13rd Dec, 2015"
    end
    end
    end
    EventsHelper::LinkifyEvent

    View Slide

  294. require "rails_helper"
    describe EventsHelper do
    describe "#linkify_event" do
    it "works" do
    self
    expect(linkify_event).to eq "RubyKaigi 11st-13rd Dec, 2015"
    end
    end
    end
    RSpec::ExampleGroups::
    EventsHelper::LinkifyEvent

    View Slide

  295. require "rails_helper"
    describe EventsHelper do
    describe "#linkify_event" do
    it "works" do
    expect(linkify_event).to eq "RubyKaigi 11st-13rd Dec, 2015"
    end
    end
    end

    View Slide

  296. Mailer Spec

    View Slide

  297. ActionMailer::TestCase
    ActionMailer::Base.deliveries
    ActionMailer::MessageDelivery
    3BJMT

    View Slide

  298. spec/mailers/*_spec.rb
    $POWFOUJPO

    View Slide

  299. email_spec
    (
    FN
    T
    Why this not in
    RSpec?

    View Slide

  300. group :test do
    gem "email_spec"
    end
    *OTUBMM

    View Slide

  301. # Either in spec/rails_helper.rb or
    # spec/email_helper.rb
    require "email_spec"
    RSpec.configure do |config|
    config.include EmailSpec::Helpers
    config.include EmailSpec::Matchers
    end
    $PO
    H

    View Slide

  302. reply_to
    deliver_to
    deliver_from
    bcc_to
    cc_to
    "1*
    have_subject
    have_body_text
    have_header

    View Slide

  303. Sent successfully
    Sent at correct time
    Sent to right person
    Email content
    (
    PBM

    View Slide

  304. class UserMailer < ActionMailer::Base
    def welcome_user(user)
    @user = user
    mail(
    to: @user.email,
    subject: "Welcome to JollyGoodCode!"
    )
    end
    end
    &YBN
    QMF

    View Slide

  305. RSpec.describe UserMailer do
    describe "#welcome_user" do
    let(:user) { build_stubbed(:user) }
    let(:email) { email_for(user) }
    def email_for(user)
    UserMailer.welcome_user(user)
    end
    it { expect(email).to have_subject "Welcome to JollyGoodCode!" }
    it { expect(email).to deliver_to user.email }
    end
    end
    &YBN
    QMF

    View Slide

  306. Job Spec

    View Slide

  307. Job invokes correct
    methods
    (
    PBM

    View Slide

  308. ActiveJob::TestCase
    ActiveJob::TestHelper
    3BJMT

    View Slide

  309. spec/jobs/*_spec.rb
    $POWFOUJPO

    View Slide

  310. it "sync repos" do
    allow(RepoSync).to receive(:new)
    RepoSyncJob.perform_now(repo)
    expect(RepoSync).to have_received(:new)
    end
    &YBN
    QMF
    Sending right messages

    View Slide

  311. it "sync repos" do
    allow(RepoSync).to receive(:new)
    RepoSyncJob.perform_now(repo)
    expect(RepoSync).to have_received(:new)
    end
    &YBN
    QMF
    Logic should be already tested
    in RepoSync specs

    View Slide

  312. View Spec

    View Slide

  313. Request Spec

    View Slide

  314. Plain Old
    Ruby Object

    View Slide

  315. Similar to
    Model Spec
    without
    loading Rails

    View Slide

  316. Interact with
    the world

    View Slide

  317. Run without network
    Avoid connectivity
    issues
    (
    PBM

    View Slide

  318. API limit
    Slower
    Test relies on internet
    Service no sandbox
    mode
    Service not exist
    8
    IZ

    View Slide

  319. spec/**/*_spec.rb
    '3
    &&%
    0
    .
    lib/github_client.rb
    spec/lib/github_client_spec.rb

    View Slide

  320. webmock
    (
    FN
    T
    Making real
    request
    IS A CRIME

    View Slide

  321. group :test do
    gem "webmock"
    end
    *OTUBMM

    View Slide

  322. require "webmock/rspec"
    WebMock.disable_net_connect!
    (allow_localhost: true)
    $PO
    H

    View Slide

  323. stub_request(
    HTTP verb, path
    ).with(
    HEADER HASH
    ).to_return(
    RESPONSE HASH
    )
    4UVCCJOH

    View Slide

  324. expect(WebMock).to
    have_requested(…).
    with(…)
    4FUUJOH&YQFDUBUJPOT

    View Slide

  325. expect(
    a_request(…)
    ).to have_been_made
    4FUUJOH&YQFDUBUJPOT

    View Slide

  326. VCR
    (
    FN
    T
    Record response from real
    API or service
    cassettes

    View Slide

  327. $ curl
    File.read
    Ruby Objects
    Faking responses

    View Slide

  328. it "webmock example" do
    url = "https://api.github.com/users/juanitofatas"
    fixture = "spec/fixtures/github/juanitofatas.json"
    # stub_request(:get, url).
    to_return(body: File.read(fixture))
    HTTP.get(url)
    expect(WebMock).to have_requested(:get, url)
    expect(a_request(:get, url)).to have_been_made
    end
    &YBN
    QMF

    View Slide

  329. &YBN
    QMF

    View Slide

  330. it "webmock example" do
    url = "https://api.github.com/users/juanitofatas"
    fixture = "spec/fixtures/github/juanitofatas.json"
    stub_request(:get, url).
    to_return(body: File.read(fixture))
    HTTP.get(url)
    expect(WebMock).to have_requested(:get, url)
    expect(a_request(:get, url)).to have_been_made
    end
    &YBN
    QMF

    View Slide

  331. it "webmock example" do
    url = "https://api.github.com/users/juanitofatas"
    fixture = "spec/fixtures/github/juanitofatas.json"
    stub_request(:get, url).
    to_return(body: File.read(fixture))
    HTTP.get(url)
    expect(WebMock).to have_requested(:get, url)
    expect(a_request(:get, url)).to have_been_made
    end
    &YBN
    QMF

    View Slide

  332. it "webmock example" do
    url = "https://api.github.com/users/juanitofatas"
    fixture = "spec/fixtures/github/juanitofatas.json"
    stub_request(:get, url).
    to_return(body: File.read(fixture))
    HTTP.get(url)
    expect(WebMock).to have_requested(:get, url)
    expect(a_request(:get, url)).to have_been_made
    end
    &YBN
    QMF

    View Slide

  333. it "webmock example" do
    url = "https://api.github.com/users/juanitofatas"
    fixture = "spec/fixtures/github/juanitofatas.json"
    stub_request(:get, url).
    to_return(body: File.read(fixture))
    HTTP.get(url)
    expect(WebMock).to have_requested(:get, url)
    expect(a_request(:get, url)).to have_been_made
    end
    &YBN
    QMF

    View Slide

  334. it "webmock example" do
    url = "https://api.github.com/users/juanitofatas"
    fixture = "spec/fixtures/github/juanitofatas.json"
    stub_request(:get, url).
    to_return(body: File.read(fixture))
    HTTP.get(url)
    expect(WebMock).to have_requested(:get, url)
    expect(a_request(:get, url)).to have_been_made
    end
    &YBN
    QMF
    https://github.com/sferik/t

    View Slide

  335. &YBN
    QMF
    many stub_request examples
    sferik/t

    View Slide

  336. Debugging

    View Slide

  337. stop spring
    pry-rails + binding.pry
    byebug everywhere
    p + puts + pp + raise

    View Slide

  338. Closing

    View Slide

  339. Routing, Request, View Specs
    Page Object (Feature Spec)
    Front-end Testing
    Infrastructure Testing
    A/B Testing
    Smoke Testing (example)

    View Slide

  340. Routing Specs
    Request Specs
    View Specs
    Page Object (Feature Spec)
    Front-end Testing
    Infrastructure Testing
    A/B Testing
    Smoke Testing (example)
    NOT
    COVERED

    View Slide

  341. LEGACY CODE

    View Slide

  342. Skip
    Pending
    v.s.
    not run at all
    run but won’t failed

    View Slide

  343. 1. Write Feature Spec
    2. New things with
    specs
    3. Use Exception
    monitoring service

    View Slide

  344. 1. Write Feature Spec
    2. New things with
    specs
    3. Use Exception
    monitoring service
    4. Find another job

    View Slide

  345. DON’T TEST
    private Method
    test the method which uses it

    View Slide

  346. Wrap Everything
    in objects
    so much easier to test

    View Slide

  347. Use shoulda to
    simplify when
    you get tired.

    View Slide

  348. Use tools to
    simplify when
    you get tired.

    View Slide

  349. Don’t aim for
    100% test
    coverage

    View Slide

  350. I get paid for code that
    works, so my philosophy is
    to test as little as possible to
    reach a given level of
    confidence.— Kent Back

    View Slide

  351. We read more
    than we write

    View Slide

  352. Anything that
    can go wrong,
    will go wrong

    View Slide

  353. That’s why
    we write tests

    View Slide

  354. Tests are also
    code. Will
    become stinky,
    break up when
    it’s time

    View Slide

  355. There are
    million ways
    of how things
    are tested

    View Slide

  356. There’s no
    tomorrow.

    View Slide

  357. WRITE
    TEST
    NOW

    View Slide

  358. A test is
    better
    than no test

    View Slide

  359. Feature
    Model
    Controller

    View Slide

  360. Do
    Code
    Review

    View Slide

  361. Team
    Style
    Guide

    View Slide

  362. Takeaways

    View Slide

  363. ALWAYS
    BE
    TESTING

    View Slide

  364. Resource

    View Slide

  365. Talks

    View Slide

  366. https://www.youtube.com/watch?v=MA4jJNUG_dI

    View Slide

  367. http://git.io/vsOKE

    View Slide

  368. https://www.youtube.com/watch?v=Libc0-0TRg4

    View Slide

  369. Books

    View Slide

  370. View Slide

  371. https://leanpub.com/rspecbook
    RUBYCONFTW2015
    ⯜顤
    8
    PSL*O1SPHSFTT
    IPROMISEYOUIWILLWRITETESTS !
    ✿䫓 !
    45RGE⑆桡⻇㔑
    D[,WCPKVQ(CVCU

    View Slide

  372. Talk to me!
    I’m happy to help you

    View Slide

  373. 6JCPM[QW
    HQT.KUVGPKPI

    View Slide

  374. 6GC6KOG

    View Slide

  375. View Slide