Test Spy for Any Object in Ruby.
- Test spy for ANY object by using
prepend
(Sorry, it runs by Ruby 2.0 or higher!) - Extremely flexible query for received messages with
received_messages
method.- By using Array and Enumerable's methods, you never have to remember the complex API and tons of the argument matchers in RSpec anymore!
- Makes mocks obsolete so you don't have to be worried about where to put the expectations (i.e. before or after the subject method).
Add this line to your application's Gemfile:
gem 'crispy'
And then execute:
$ bundle
Or install it yourself as:
$ gem install crispy
>> require 'crispy'
>> include Crispy # import spy, spy_into and any other functions from Crispy namespace.
>> object = YourCoolClass.new
>> spy_into object # sneak into your object to spy.
# Use your object as usual.
>> object.your_cool_method 1, 2, 3
>> object.your_method_without_argument
>> object.your_lovely_method 'great', 'arguments'
>> object.your_lovely_method 'great', 'arguments', 'again'
>> object.your_finalizer 'resource to release'
Call query methods through the spy object, instead of YourCoolClass's instance.
>> spy(object).received? :your_cool_method
=> true
>> spy(object).received? :your_method_without_argument
=> true
>> spy(object).received? :your_lovely_method
=> true
>> spy(object).received? :your_ugly_method
=> false
Each argument is compared by ==
method.
>> spy(object).received? :your_cool_method, 1, 2, 3
=> true
>> spy(object).received? :your_cool_method, 0, 0, 0
=> false
>> spy(object).received? :your_method_without_argument, :not, :given, :arguments
=> false
>> spy(object).received? :your_lovely_method, 'great', 'arguments'
=> true
>> spy(object).received? :your_ugly_method, 'of course', 'I gave no arguments'
=> false
Sorry, I'm still thinking of the specification for that case.
>> spy(object).count_received :your_cool_method
=> 1
>> spy(object).count_received :your_cool_method, 1, 2, 3
=> 1
>> spy(object).count_received :your_cool_method, 0, 0, 0
=> 0
You can check arbitrary received methods with the familliar Array's (and of course including Enumerable's!) methods such as any?
, all
, first
, []
, index
.
Because spy(object).received_messages
returns an array of CrispyReceivedMessage
instances.
You don't have to remember the tons of matchers for received arguments any more!!
>>
spy(object).received_messages.any? do|m|
m.method_name == :your_cool_method && m.arguments.all? {|arg| arg.is_a? Integer }
end
=> true
>> last_method_call = spy(object).received_messages.last
>>
last_method_call.method_name == :your_finalizer &&
last_method_call.arguments == ['resource to release']
=> true
>> spy(object).stub(:your_cool_method, 'Awesome!')
>> object.your_cool_method
=> "Awesome!"
>> spy(object).stub(your_lovely_method: 'I love this method!', your_finalizer: 'Finalized!')
>> object.your_lovely_method
=> "I love this method!"
>> object.your_finalizer
=> "Finalized!"
Of cource stubbed methods are spied as well.
>> spy(object).received? :your_cool_method
=> true
# `spy(object)` keeps its spied log of a method even after stubbing the method.
>> spy(object).count_received :your_lovely_method
=> 3
>> spy_into_instances(YourCoolClass)
>> instance1 = YourCoolClass.new
>> instance2 = YourCoolClass.new
>> instance1.your_cool_method 'and', 'args'
>> instance2.your_lovely_method
>> instance2.your_lovely_method 'again!'
>> instance1.your_finalizer 'cleaning up...'
You can check methods called by all instances of a class by the same query methods wth spy.
>> spy_of_instances(YourCoolClass).received? :your_cool_method, 'and', 'args'
=> true
>> spy_of_instances(YourCoolClass).count_received :your_lovely_method
=> 2
>> spy_of_instances(YourCoolClass).received_messages.last.method_name == :your_finalizer
=> true
In addition, you can check which instance calles a method as well as its arguments.
>> spy_of_instances(YourCoolClass).received_with_receiver? instance1, :your_cool_method
=> true
>> spy_of_instances(YourCoolClass).received_with_receiver? instance2, :your_cool_method
=> false
>> spy_of_instances(YourCoolClass).count_received_with_receiver instance1, :your_lovely_method
=> 0
>> spy_of_instances(YourCoolClass).count_received_with_receiver instance2, :your_lovely_method
=> 2
>> spy_of_instances(YourCoolClass).received_messages_with_receiver.last.receiver == instance1
=> true
Note that spy_of_instances
stops spying after called methods with with_receiver
(or with_receiver?
) prefix.
This is to prevent the spy from unexpectedly logging methods used to compare its receiver (such as ==
).
>> spy_into_instances(YourCoolClass::Again)
>> instance = YourCoolClass::Again.new
>> instance.your_another_method
>> # Stops spying here.
>> spy_of_instances(YourCoolClass::Again).received_with_receiver? instance, :your_another_method
>> # Perhaps you don't want to log methods in test code.
>> instance.some_method_for_testing
>> spy_of_instances(YourCoolClass::Again).received? :some_method_for_testing
=> false
If you want to restart spying, use restart
method literally.
>> spy_of_instances(YourCoolClass::Again).restart
>> instance.some_method_for_testing
>> spy_of_instances(YourCoolClass::Again).received? :some_method_for_testing
=> true
>> spy_of_instances(YourCoolClass).stub(your_lovely_method: 'Even more lovely!', your_cool_method: 'much cooler!')
>> instance1.your_lovely_method
=> "Even more lovely!"
>> instance2.your_cool_method
=> "much cooler!"
Double can call Spy's method directly.
You do NOT need to write code such as spy(your_double).stub(...)
.
Just your_double.stub(...)
.
>> your_awesome_double = double('your awesome double', nice!: '+1!', sexy?: true)
>> your_awesome_double.nice!
=> "+1!"
>> your_awesome_double.sexy?
=> true
>> your_awesome_double.stub(:another_method, 'can be stubbed.')
>> your_awesome_double.another_method
=> "can be stubbed."
A double is spied without spy_into
-ing.
And as double.stub(...)
, Double can also call Spy's method such as received?
>> your_awesome_double.received? :nice!
=> true
>> your_awesome_double.count_received :another_method
=> 1
Specify the fully qualified name of the constant instead of the constant itself.
>> YourCoolClass::YOUR_COOL_CONST
=> "value before stubbed"
>> stub_const 'YourCoolClass::YOUR_COOL_CONST', 'more cool value!'
>> YourCoolClass::YOUR_COOL_CONST
=> "more cool value!"
Sometimes, you may want to make a spy ignore some methods.
>> x = YourCoolClass.new
>> spy_into x, except: [:method_to_ignore1, :method_to_ignore2]
>> x.method_to_ignore1
>> x.method_to_ignore2
>> x.your_cool_method
>> spy(x).received? :method_to_ignore1
=> false
>> spy(x).received? :method_to_ignore2
=> false
>> spy(x).received? :your_cool_method
=> true
Remember to reset all the changes made by Crispy, call CrispyWorld.reset
.
>> CrispyWorld.reset
>> spy(object).count_received :your_cool_method
=> 0
>> spy(object).count_received :your_lovely_method
=> 0
>> spy(object).received? :your_finalizer
=> false
>> object.your_cool_method
=> "cool!"
>> object.your_lovely_method
=> "lovely!"
>> YourCoolClass::YOUR_COOL_CONST
=> "value before stubbed"
- Fork it ( https://github.com/igrep/crispy/fork )
- Create your feature branch (
git checkout -b your-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin your-new-feature
) - Create a new Pull Request