📜 ⬆️ ⬇️

BDD / TDD - Learning to write matchmakers

Behavioral specification development (BDD) is one of the cornerstones of the Ruby philosophy.

A very successful implementation of BDD is the familiar RSpec . One of the great features of RSpec is its extensibility.

So, we use RSpec in conjunction with Factory Girl (although it would be worth switching to Machinist ), RR (perfect frame for plugs), Spork, and recently switched from Shoulda to Remarkable .
')
Naturally, I wanted to immediately make our specs easier to understand. For example, we want to check the presence of callback definitions in the specs.

A good way to do this is to create a new “waiter” (matcher).



The main logic is the is_valid? Method, which checks the test subject's compliance with our expectations.

  1. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  2. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  3. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  4. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  5. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  6. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  7. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  8. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  9. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  10. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  11. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  12. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  13. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  14. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  15. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  16. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  17. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  18. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  19. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  20. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  21. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  22. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  23. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )
  24. module Scalaxy module Matchers class CallbackPresenceMatcher < Remarkable::Base arguments :chain , :callback assertion :is_valid ? protected def is_valid? instance = @subject . kind_of ? ( Class ) ? @subject . new : @subject chain_name = "#{@chain}_callback_chain" . to_sym return false unless instance. kind_of ? ( ActiveSupport::Callbacks ) return false unless instance. class . respond_to ? ( @chain ) return false unless instance. class . respond_to ? ( chain_name ) return false if instance. class . send ( chain_name ) . select { | c | c. method == @callback } . empty ? true end end # Matches for presence of specified callback inside specified callback chain. # should_callback_presence :after_run, :notify_people def callback_presence ( * args ) CallbackPresenceMatcher. new ( * args ) . spec ( self ) end end end Remarkable. include_matchers ! ( Scalaxy, ActiveSupport::TestCase )


You will also need to expand the locale file by adding information about our "waiter".
  1. en:
  2. scalaxy:
  3. callback_presence:
  4. description: 'Checks for presence of {{callback}} in specified callback chain {{chain}}.'
  5. expectations:
  6. is_valid: "to be valid when {{callback}} in {{chain}}"


And add require on our matchers.rb file in spec_helper.

And now you can easily describe our expectations.
  1. class Thing < ActiveRecord :: Base
  2. ## Callbacks
  3. define_callbacks : after_run
  4. after_run : notify_people
  5. end

  1. describe Thing do
  2. should_callback_presence : after_run ,: notify_people
  3. end


______________________
The text was prepared in the Habr Editor from © SoftCoder.ru

Source: https://habr.com/ru/post/73862/


All Articles