#RailsPacific - Taming Chaotic Specs - RSpec Design Patterns

#RailsPacific - Taming Chaotic Specs - RSpec Design Patterns

Don’t you hate when testing takes 3x as long because your specs are hard to understand? Following a few simple patterns, you can easily take a bloated spec and make it DRY and simple to extend. We will take a bloated sample spec and refactor it to something manageable, readable and concise.

## With Presenter Notes ##
https://speakerdeck.com/acuppy/number-railspacific-notes-taming-chaotic-specs-rspec-design-patterns

Fff206665889a0f5baeedf5b75483edd?s=128

Adam Cuppy

May 20, 2016
Tweet

Transcript

  1. www.codingzeal.com @adamcuppy Adam Cuppy

  2. github.com/acuppy twitter.com/adamcuppy

  3. speakerdeck.com/ acuppy

  4. http://rspec.info/

  5. Problem

  6. Taming Chaotic Specs RSpec Design Patterns

  7. Not what to test, But…

  8. Patterns to follow when structuring the test(s)

  9. 1. Communicate expectations 2. Encourage consistency 3. Reduce the mental

    load Design Patterns…
  10. Minimum Valid Object

  11. 1. Start with a “valid” object 2. Make one specific

    change 3. Assert the “valid” object is now “invalid” M.V.O. Workflow…
  12. # app/models/user.rb class User < ActiveRecord::Base has_secure_password validates :firstname, presence:

    true, length: 4..20 validates :middlename, length: 4..20, allow_blank: true validates :lastname, presence: true, length: 4..20 validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i } validates :password, presence: true, confirmation: true, length: { minimum: 8 } # ... end
  13. # spec/models/user_spec.rb describe User do it "should be invalid" do

    user = User.new(firstname: "really really ... super long firstname") expect(user.valid?).to_not eq true user = User.new(firstname: "sht") expect(user.valid?).to_not eq true end end
  14. # spec/models/user_spec.rb describe User do it "should be invalid" do

    user = User.new(firstname: "really really ... super long firstname") expect(user.valid?).to_not eq true user = User.new(firstname: "sht") expect(user.valid?).to_not eq true end end
  15. # spec/models/user_spec.rb describe User do it "should be invalid" do

    user = User.new(firstname: "really really ... super long firstname") expect(user.valid?).to_not eq true user = User.new(firstname: "sht") expect(user.valid?).to_not eq true end end
  16. # spec/models/user_spec.rb describe User do it "should be invalid" do

    user = User.new(firstname: "really really ... super long firstname") expect(user.valid?).to_not eq true user = User.new(firstname: "sht") expect(user.valid?).to_not eq true end end
  17. # app/models/user.rb class User < ActiveRecord::Base has_secure_password validates :firstname, presence:

    true, length: 4..20 validates :middlename, length: 4..20, allow_blank: true validates :lastname, presence: true, length: 4..20 validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i } validates :password, presence: true, confirmation: true, length: { minimum: 8 } # ... end
  18. # app/models/user.rb class User < ActiveRecord::Base has_secure_password validates :firstname, presence:

    true, length: 4..20 validates :middlename, length: 4..20, allow_blank: true validates :lastname, presence: true, length: 4..20 validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i } validates :password, presence: true, confirmation: true, length: { minimum: 8 } # ... end
  19. # app/models/user.rb class User < ActiveRecord::Base has_secure_password validates :firstname, presence:

    true, length: 4..20 validates :middlename, length: 4..20, allow_blank: true validates :lastname, presence: true, length: 4..20 validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i } validates :password, presence: true, confirmation: true, length: { minimum: 8 } # ... end Other validations failing!!
  20. # spec/models/user_spec.rb describe User do it "should be invalid" do

    user = User.new(firstname: "really really ... super long firstname") expect(user.valid?).to_not eq true user = User.new(firstname: "sht") expect(user.valid?).to_not eq true end end
  21. # spec/models/user_spec.rb describe User do it "should be invalid" do

    user = User.new(firstname: "really really ... super long firstname") expect(user.valid?).to_not eq true user = User.new(firstname: "sht") expect(user.valid?).to_not eq true end end
  22. describe User do # ... end

  23. describe User do subject(:user) { described_class.new } # ... end

  24. describe User do subject(:user) { described_class.new } # ... end

  25. describe User do subject(:user) { described_class.new } # ... end

  26. # spec/models/user_spec.rb describe User do it "should be invalid" do

    user = User.new(firstname: "really really ... super long firstname") expect(user.valid?).to_not eq true user = User.new(firstname: "sht") expect(user.valid?).to_not eq true end end
  27. # spec/models/user_spec.rb describe User do it "should be invalid" do

    user = User.new(firstname: "really really ... super long firstname") expect(user.valid?).to_not eq true user = User.new(firstname: "sht") expect(user.valid?).to_not eq true end end
  28. describe User do subject(:user) { described_class.new } # ... end

  29. describe User do subject(:user) { described_class.new } context 'with a

    firstname that is over 20 chars' do # ... end context 'with a firstname that is under 4 chars' do # ... end end
  30. describe User do subject(:user) { described_class.new } context 'with a

    firstname that is over 20 chars' do # ... end context 'with a firstname that is under 4 chars' do # ... end end Too Long
  31. describe User do subject(:user) { described_class.new } context 'with a

    firstname that is over 20 chars' do # ... end context 'with a firstname that is under 4 chars' do # ... end end Too Short
  32. describe User do subject(:user) { described_class.new } context 'with a

    firstname that is over 20 chars' do # ... end context 'with a firstname that is under 4 chars' do # ... end end
  33. describe User do subject(:user) { described_class.new firstname: firstname } context

    'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } # ... end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } # ... end end
  34. describe User do subject(:user) { described_class.new firstname: firstname } context

    'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } # ... end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } # ... end end
  35. # spec/models/user_spec.rb describe User do it "should be invalid" do

    user = User.new(firstname: "really really ... super long firstname") expect(user.valid?).to_not eq true user = User.new(firstname: "sht") expect(user.valid?).to_not eq true end end
  36. # spec/models/user_spec.rb describe User do it "should be invalid" do

    user = User.new(firstname: "really really ... super long firstname") expect(user.valid?).to_not eq true user = User.new(firstname: "sht") expect(user.valid?).to_not eq true end end
  37. # spec/models/user_spec.rb describe User do it "should be invalid" do

    user = User.new(firstname: "really really ... super long firstname") expect(user.valid?).to_not eq true user = User.new(firstname: "sht") expect(user.valid?).to_not eq true end end
  38. expect(user.valid?).to_not eq true (true, not true?)

  39. expect(user.valid?).to_not eq true (true, not true?)

  40. RSpec Predicate Magic Methods

  41. user.valid?

  42. user.valid? expect(user).to_not be_valid

  43. user.valid? expect(user).to_not be_valid

  44. user.riding_a_monkey? expect(user).to_not be_riding_a_monkey

  45. (true, not true?) expect(user).to_not be_valid

  46. (true?) expect(user).to be_invalid

  47. (true?) expect(user).to be_invalid

  48. describe User do subject(:user) { described_class.new firstname: firstname } context

    'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } # ... end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } # ... end end
  49. describe User do subject(:user) { described_class.new firstname: firstname } context

    'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end
  50. describe User do subject(:user) { described_class.new firstname: firstname } context

    'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end Fix false positive ???
  51. describe User do subject(:user) { described_class.new firstname: firstname } context

    'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end
  52. describe User do subject(:user) { described_class.new firstname: firstname } context

    'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end
  53. describe User do subject(:user) { described_class.new firstname: firstname } let(:firstname)

    { 'Adam' } specify { expect(user).to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end
  54. describe User do subject(:user) { described_class.new firstname: firstname } let(:firstname)

    { 'Adam' } specify { expect(user).to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end ??
  55. describe User do subject(:user) { described_class.new firstname: firstname } let(:firstname)

    { 'Adam' } specify { expect(user).to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end
  56. describe User do subject(:user) { described_class.new firstname: firstname } let(:firstname)

    { 'Adam' } specify { expect(user).to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end ?? ??
  57. describe User do subject(:user) { described_class.new firstname: firstname } let(:firstname)

    { 'Adam' } specify { expect(user).to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end
  58. describe User do subject(:user) { described_class.new firstname: firstname } let(:firstname)

    { 'Adam' } specify { expect(user).to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end Fix false positive ???
  59. describe User do subject(:user) { described_class.new firstname: firstname } let(:firstname)

    { 'Adam' } specify { expect(user).to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end
  60. describe User do subject(:user) { described_class.new firstname: firstname } let(:firstname)

    { 'Adam' } specify { expect(user).to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } specify { expect(user).to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } specify { expect(user).to be_invalid } end end
  61. describe User do subject(:user) { described_class.new firstname: firstname } let(:firstname)

    { 'Adam' } it { is_expected.to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } it { is_expected.to be_invalid } end context 'with a firstname that is under 4 chars' do let(:firstname) { 'srt' } it { is_expected.to be_invalid } end end
  62. describe User do subject(:user) { described_class.new firstname: firstname, middlename: middlename,

    lastname: lastname, email: email, password: password } let(:firstname) { 'Adam' } let(:middlename) { nil } let(:lastname) { 'Cuppy' } let(:email) { 'adam@codingzeal.com' } let(:password) { 'Passw0rd!' } it { is_expected.to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } it { is_expected.to be_invalid } end # ... end
  63. describe User do subject(:user) { described_class.new firstname: firstname, middlename: middlename,

    lastname: lastname, email: email, password: password } let(:firstname) { 'Adam' } let(:middlename) { nil } let(:lastname) { 'Cuppy' } let(:email) { 'adam@codingzeal.com' } let(:password) { 'Passw0rd!' } it { is_expected.to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } it { is_expected.to be_invalid } end # ... end #1: Build the subject
  64. describe User do subject(:user) { described_class.new firstname: firstname, middlename: middlename,

    lastname: lastname, email: email, password: password } let(:firstname) { 'Adam' } let(:middlename) { nil } let(:lastname) { 'Cuppy' } let(:email) { 'adam@codingzeal.com' } let(:password) { 'Passw0rd!' } it { is_expected.to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } it { is_expected.to be_invalid } end # ... end #2: Set the config values
  65. describe User do subject(:user) { described_class.new firstname: firstname, middlename: middlename,

    lastname: lastname, email: email, password: password } let(:firstname) { 'Adam' } let(:middlename) { nil } let(:lastname) { 'Cuppy' } let(:email) { 'adam@codingzeal.com' } let(:password) { 'Passw0rd!' } it { is_expected.to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } it { is_expected.to be_invalid } end # ... end #3 Assert a Valid state
  66. describe User do subject(:user) { described_class.new firstname: firstname, middlename: middlename,

    lastname: lastname, email: email, password: password } let(:firstname) { 'Adam' } let(:middlename) { nil } let(:lastname) { 'Cuppy' } let(:email) { 'adam@codingzeal.com' } let(:password) { 'Passw0rd!' } it { is_expected.to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } it { is_expected.to be_invalid } end # ... end #4 Mutate
  67. describe User do subject(:user) { described_class.new firstname: firstname, middlename: middlename,

    lastname: lastname, email: email, password: password } let(:firstname) { 'Adam' } let(:middlename) { nil } let(:lastname) { 'Cuppy' } let(:email) { 'adam@codingzeal.com' } let(:password) { 'Passw0rd!' } it { is_expected.to be_valid } context 'with a firstname that is over 20 chars' do let(:firstname) { 'really really ... super long firstname' } it { is_expected.to be_invalid } end # ... end
  68. Permutation Tables

  69. 1. Define sets of data 2. Define the output of

    each set 3. Assert the method creates the output from the data (input/output) P.T. Workflow…
  70. class User < ActiveRecord::Base # ... def fullname [firstname, middlename,

    lastname].compact.join ' ' end end
  71. class User < ActiveRecord::Base # ... def fullname [firstname, middlename,

    lastname].compact.join ' ' end end
  72. class User < ActiveRecord::Base # ... def fullname [firstname, middlename,

    lastname].compact.join ' ' end end
  73. class User < ActiveRecord::Base # ... def fullname [firstname, middlename,

    lastname].compact.join ' ' end end
  74. describe User do # ... describe ‘#fullname' do subject(:fullname) {

    user.fullname } context 'when firstname is blank' do let(:firstname) { nil } it { is_expected.to eq lastname } end context 'when lastname is blank' do let(:lastname) { nil } it { is_expected.to eq firstname } end context 'when first and lastname are blank' do let(:firstname) { nil } let(:lastname) { nil } it { is_expected.to be_blank } end context 'when there are all three names' do let(:middlename) { 'Jimmy' } it { is_expected.to eq "#{firstname} #{middlename} #{lastname}" } end end # ... end
  75. describe User do # ... describe ‘#fullname' do subject(:fullname) {

    user.fullname } context 'when firstname is blank' do let(:firstname) { nil } it { is_expected.to eq lastname } end context 'when lastname is blank' do let(:lastname) { nil } it { is_expected.to eq firstname } end context 'when first and lastname are blank' do let(:firstname) { nil } let(:lastname) { nil } it { is_expected.to be_blank } end context 'when there are all three names' do let(:middlename) { 'Jimmy' } it { is_expected.to eq "#{firstname} #{middlename} #{lastname}" } end end # ... end
  76. describe '#fullname' do subject(:fullname) { user.fullname } # ... end

  77. describe User do # ... describe ‘#fullname' do subject(:fullname) {

    user.fullname } context 'when firstname is blank' do let(:firstname) { nil } it { is_expected.to eq lastname } end context 'when lastname is blank' do let(:lastname) { nil } it { is_expected.to eq firstname } end context 'when first and lastname are blank' do let(:firstname) { nil } let(:lastname) { nil } it { is_expected.to be_blank } end context 'when there are all three names' do let(:middlename) { 'Jimmy' } it { is_expected.to eq "#{firstname} #{middlename} #{lastname}" } end end # ... end
  78. describe User do # ... describe ‘#fullname' do subject(:fullname) {

    user.fullname } context 'when firstname is blank' do let(:firstname) { nil } it { is_expected.to eq lastname } end context 'when lastname is blank' do let(:lastname) { nil } it { is_expected.to eq firstname } end context 'when first and lastname are blank' do let(:firstname) { nil } let(:lastname) { nil } it { is_expected.to be_blank } end context 'when there are all three names' do let(:middlename) { 'Jimmy' } it { is_expected.to eq "#{firstname} #{middlename} #{lastname}" } end end # ... end
  79. describe User do # ... describe ‘#fullname' do subject(:fullname) {

    user.fullname } context 'when firstname is blank' do let(:firstname) { nil } it { is_expected.to eq lastname } end context 'when lastname is blank' do let(:lastname) { nil } it { is_expected.to eq firstname } end context 'when first and lastname are blank' do let(:firstname) { nil } let(:lastname) { nil } it { is_expected.to be_blank } end context 'when there are all three names' do let(:middlename) { 'Jimmy' } it { is_expected.to eq "#{firstname} #{middlename} #{lastname}" } end end # ... end Anything missing ??
  80. describe User do # ... describe ‘#fullname' do subject(:fullname) {

    user.fullname } context 'when firstname is blank' do let(:firstname) { nil } it { is_expected.to eq lastname } end context 'when lastname is blank' do let(:lastname) { nil } it { is_expected.to eq firstname } end context 'when first and lastname are blank' do let(:firstname) { nil } let(:lastname) { nil } it { is_expected.to be_blank } end context 'when there are all three names' do let(:middlename) { 'Jimmy' } it { is_expected.to eq "#{firstname} #{middlename} #{lastname}" } end end # ... end
  81. describe ‘#fullname' do subject(:fullname) { user.fullname } context 'when firstname

    is blank' do let(:firstname) { nil } it { is_expected.to eq lastname } end #... end
  82. describe ‘#fullname' do subject(:fullname) { user.fullname } context 'when firstname

    is blank' do let(:firstname) { nil } it { is_expected.to eq lastname } end #... end
  83. describe ‘#fullname' do subject(:fullname) { user.fullname } context 'when firstname

    is blank' do let(:firstname) { nil } it { is_expected.to eq lastname } end #... end
  84. describe ‘#fullname' do # context 'when firstname is blank' do

    # let(:firstname) { nil } # it { is_expected.to eq lastname } # end { [nil, 'James', 'Johnson'] => 'James Johnson', }.each do |name_set, output| # ... end end Firstname
  85. describe ‘#fullname' do # context 'when firstname is blank' do

    # let(:firstname) { nil } # it { is_expected.to eq lastname } # end { [nil, 'James', 'Johnson'] => 'James Johnson', }.each do |name_set, output| # ... end end middlename
  86. describe ‘#fullname' do # context 'when firstname is blank' do

    # let(:firstname) { nil } # it { is_expected.to eq lastname } # end { [nil, 'James', 'Johnson'] => 'James Johnson', }.each do |name_set, output| # ... end end Lastname
  87. describe ‘#fullname' do # context 'when firstname is blank' do

    # let(:firstname) { nil } # it { is_expected.to eq lastname } # end { [nil, 'James', 'Johnson'] => 'James Johnson', }.each do |name_set, output| # ... end end Fullname
  88. describe ‘#fullname' do # ... { [nil, 'James', 'Johnson'] =>

    'James Johnson', [nil, 'James', nil] => 'James', ['Jimmy', 'James', nil] => 'Jimmy James', ['Jimmy', 'James', 'Johnson'] => 'Jimmy James Johnson' }.each do |name_set, output| # ... end end
  89. describe ‘#fullname' do # ... { [nil, 'James', 'Johnson'] =>

    'James Johnson', [nil, 'James', nil] => 'James', ['Jimmy', 'James', nil] => 'Jimmy James', ['Jimmy', 'James', 'Johnson'] => 'Jimmy James Johnson' }.each do |name_set, output| # ... end end Anything missing ??
  90. describe ‘#fullname' do # ... { [nil, 'James', 'Johnson'] =>

    'James Johnson', [nil, 'James', nil] => 'James', ['Jimmy', 'James', nil] => 'Jimmy James', ['Jimmy', 'James', 'Johnson'] => 'Jimmy James Johnson' ['Jimmy', nil, nil] => 'Jimmy', [nil, nil, 'Johnson'] => 'Johnson', ['Jimmy', nil, 'Johnson'] => 'Jimmy Johnson' }.each do |name_set, output| # ... end end
  91. describe ‘#fullname' do shared_examples_for 'a fullname' do |(first, middle, last),

    output| subject(:fullname) { user.fullname } let(:firstname) { first } let(:middlename) { middle } let(:lastname) { last } it { is_expected.to eq output } end { [nil, 'James', 'Johnson'] => 'James Johnson', [nil, 'James', nil] => 'James', ['Jimmy', 'James', nil] => 'Jimmy James', ['Jimmy', 'James', 'Johnson'] => 'Jimmy James Johnson' ['Jimmy', nil, nil] => 'Jimmy', [nil, nil, 'Johnson'] => 'Johnson', ['Jimmy', nil, 'Johnson'] => 'Jimmy Johnson' }.each do |name_set, output| it_behaves_like 'a fullname', name_set, output end end
  92. describe ‘#fullname' do shared_examples_for 'a fullname' do |(first, middle, last),

    output| subject(:fullname) { user.fullname } let(:firstname) { first } let(:middlename) { middle } let(:lastname) { last } it { is_expected.to eq output } end { [nil, 'James', 'Johnson'] => 'James Johnson', [nil, 'James', nil] => 'James', ['Jimmy', 'James', nil] => 'Jimmy James', ['Jimmy', 'James', 'Johnson'] => 'Jimmy James Johnson' ['Jimmy', nil, nil] => 'Jimmy', [nil, nil, 'Johnson'] => 'Johnson', ['Jimmy', nil, 'Johnson'] => 'Jimmy Johnson' }.each do |name_set, output| it_behaves_like 'a fullname', name_set, output end end
  93. describe ‘#fullname' do shared_examples_for 'a fullname' do |(first, middle, last),

    output| subject(:fullname) { user.fullname } let(:firstname) { first } let(:middlename) { middle } let(:lastname) { last } it { is_expected.to eq output } end { [nil, 'James', 'Johnson'] => 'James Johnson', [nil, 'James', nil] => 'James', ['Jimmy', 'James', nil] => 'Jimmy James', ['Jimmy', 'James', 'Johnson'] => 'Jimmy James Johnson' ['Jimmy', nil, nil] => 'Jimmy', [nil, nil, 'Johnson'] => 'Johnson', ['Jimmy', nil, 'Johnson'] => 'Jimmy Johnson' }.each do |name_set, output| it_behaves_like 'a fullname', name_set, output end end
  94. describe ‘#fullname' do shared_examples_for 'a fullname' do |(first, middle, last),

    output| subject(:fullname) { user.fullname } let(:firstname) { first } let(:middlename) { middle } let(:lastname) { last } it { is_expected.to eq output } end { [nil, 'James', 'Johnson'] => 'James Johnson', [nil, 'James', nil] => 'James', ['Jimmy', 'James', nil] => 'Jimmy James', ['Jimmy', 'James', 'Johnson'] => 'Jimmy James Johnson' ['Jimmy', nil, nil] => 'Jimmy', [nil, nil, 'Johnson'] => 'Johnson', ['Jimmy', nil, 'Johnson'] => 'Jimmy Johnson' }.each do |name_set, output| it_behaves_like 'a fullname', name_set, output end end
  95. Golden Master

  96. 1. Backfilling untested legacy code 2. Uncertain expectations require visual

    confirmation 3. Code complexity significantly exceeds current domain knowledge Golden Master Testing
  97. require 'rails_helper' RSpec.describe User, type: :model do pending "add some

    examples to (or delete) #{__FILE__}" end
  98. 1. Take a snapshot of an object (to a file)

    2. Verify the snapshot (manually) 3. Compare future versions to the verified “master” Golden Master Testing Workflow…
  99. https://github.com/kytrinyx/approvals

  100. # spec/spec_helper.rb require ‘approvals/rspec' # path/to/spec.rb it 'works' do verify

    format: :html do "<html><head></head><body><h1>ZOMG</h1></body></html>" end end # Manually verify snapshots $ cd /path/to/app $ approvals verify https://github.com/kytrinyx/approvals
  101. # spec/spec_helper.rb require ‘approvals/rspec' # path/to/spec.rb it 'works' do verify

    format: :html do "<html><head></head><body><h1>ZOMG</h1></body></html>" end end # Manually verify snapshots $ cd /path/to/app $ approvals verify https://github.com/kytrinyx/approvals
  102. # spec/spec_helper.rb require ‘approvals/rspec' # path/to/spec.rb it 'works' do verify

    format: :html do "<html><head></head><body><h1>ZOMG</h1></body></html>" end end # Manually verify snapshots $ cd /path/to/app $ approvals verify https://github.com/kytrinyx/approvals
  103. Best Practices

  104. Best Practices Better

  105. Best Practices Better Good, but not Required

  106. Best Practices Better Good, but not Required Other ways of

    doing things, and why it might be helpfuL…ideas
  107. Best Practices Better Good, but not Required Other ways of

    doing things, and why it might be helpfuL…ideas
  108. let (not @instance vars)

  109. @fullname = 'Adam Cuppy' specify { expect(@fullnmae).to eq 'Adam Cuppy'

    }
  110. @fullname = 'Adam Cuppy' specify { expect(@fullnmae).to eq 'Adam Cuppy'

    } # => expected: "Adam Cuppy", got: nil ???
  111. @fullname = 'Adam Cuppy' specify { expect(@fullnmae).to eq 'Adam Cuppy'

    }
  112. @fullname = 'Adam Cuppy' specify { expect(@fullnmae).to eq 'Adam Cuppy'

    } !!!
  113. let(:fullname) { 'Adam Cuppy' } specify { expect(fullnmae).to eq 'Adam

    Cuppy' }
  114. let(:fullname) { 'Adam Cuppy' } specify { expect(fullnmae).to eq 'Adam

    Cuppy' } # => NameError: undefined local variable or method `fullnmae'
  115. Descriptive Naming

  116. it { expect(user1).to be_valid } it { expect(user2).to be_invalid }

  117. it { expect(user).to be_valid } it { expect(user_with_duplicate_email).to be_invalid }

  118. describe 'validations' do subject(:user) { User.new email: 'joe@example.com' } it

    { is_expected.to be_valid } context 'when an email is used' do let(:user_with_duplicate_email) do User.new email: user.email end it { expect(user_with_duplicate_email).to be_invalid } end end
  119. describe 'validations' do subject(:user) { User.new email: 'joe@example.com' } it

    { is_expected.to be_valid } context 'when an email is used' do let(:user_with_duplicate_email) do User.new email: user.email end it { expect(user_with_duplicate_email).to be_invalid } end end
  120. describe 'validations' do subject(:user) { User.new email: 'joe@example.com' } it

    { is_expected.to be_valid } context 'when an email is used' do let(:user_with_duplicate_email) do User.new email: user.email end it { expect(user_with_duplicate_email).to be_invalid } end end
  121. describe 'validations' do subject(:user) { User.new email: 'joe@example.com' } it

    { is_expected.to be_valid } context 'when an email is used' do let(:user_with_duplicate_email) do User.new email: user.email end it { expect(user_with_duplicate_email).to be_invalid } end end
  122. Extract Common Expectations

  123. Custom Matchers

  124. require 'rspec/expectations' RSpec::Matchers.define :be_a_multiple_of do |expected| match do |actual| actual

    % expected == 0 end end RSpec.describe 9 do it { is_expected.to be_a_multiple_of(3) } end https://www.relishapp.com/rspec/rspec-expectations/v/3-3/docs/custom-matchers/define-matcher
  125. require 'rspec/expectations' RSpec::Matchers.define :be_a_multiple_of do |expected| match do |actual| actual

    % expected == 0 end end RSpec.describe 9 do it { is_expected.to be_a_multiple_of(3) } end https://www.relishapp.com/rspec/rspec-expectations/v/3-3/docs/custom-matchers/define-matcher
  126. Factories over Fixtures

  127. https://github.com/thoughtbot/factory_girl

  128. describe User do subject(:user) { User.new } # ... end

  129. describe User do subject(:user) { FactoryGirl.build :user } # ...

    end
  130. describe User do subject(:user) { FactoryGirl.build :user } # ...

    end
  131. FactoryGirl.lint!

  132. https://github.com/thoughtbot/factory_girl

  133. Readables

  134. http://betterspecs.org/

  135. Randy Coulman - http://randycoulman.com/blog/categories/getting-testy/

  136. Randy Coulman - http://randycoulman.com/blog/categories/getting-testy/

  137. Sandi Metz - http://www.poodr.com

  138. https://codingzeal.com/

  139. www.codingzeal.com @adamcuppy Adam Cuppy