Upgrade to Pro — share decks privately, control downloads, hide ads and more …

#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

Adam Cuppy (he/him)

May 20, 2016
Tweet

More Decks by Adam Cuppy (he/him)

Other Decks in Programming

Transcript

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

    change 3. Assert the “valid” object is now “invalid” M.V.O. Workflow…
  2. # 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
  3. # 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
  4. # 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
  5. # 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
  6. # 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
  7. # 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
  8. # 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
  9. # 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!!
  10. # 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
  11. # 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
  12. # 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
  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. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  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. # 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
  23. 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
  24. 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
  25. 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 ???
  26. 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
  27. 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
  28. 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
  29. 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 ??
  30. 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
  31. 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 ?? ??
  32. 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
  33. 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 ???
  34. 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
  35. 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
  36. 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
  37. 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) { '[email protected]' } 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
  38. 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) { '[email protected]' } 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
  39. 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) { '[email protected]' } 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
  40. 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) { '[email protected]' } 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
  41. 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) { '[email protected]' } 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
  42. 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) { '[email protected]' } 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
  43. 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…
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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 ??
  49. 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
  50. describe ‘#fullname' do subject(:fullname) { user.fullname } context 'when firstname

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

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

    is blank' do let(:firstname) { nil } it { is_expected.to eq lastname } end #... end
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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 ??
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 1. Backfilling untested legacy code 2. Uncertain expectations require visual

    confirmation 3. Code complexity significantly exceeds current domain knowledge Golden Master Testing
  65. 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…
  66. # 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
  67. # 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
  68. # 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
  69. Best Practices Better Good, but not Required Other ways of

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

    doing things, and why it might be helpfuL…ideas
  71. let(:fullname) { 'Adam Cuppy' } specify { expect(fullnmae).to eq 'Adam

    Cuppy' } # => NameError: undefined local variable or method `fullnmae'
  72. describe 'validations' do subject(:user) { User.new email: '[email protected]' } 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
  73. describe 'validations' do subject(:user) { User.new email: '[email protected]' } 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
  74. describe 'validations' do subject(:user) { User.new email: '[email protected]' } 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
  75. describe 'validations' do subject(:user) { User.new email: '[email protected]' } 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
  76. 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
  77. 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