Crispy
Test Spy for Any Object in Ruby.
Features
- 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).
Installation
Add this line to your application's Gemfile:
gem 'crispy'
And then execute:
$ bundle
Or install it yourself as:
$ gem install crispy
Usage
Spy on an Object
>> require 'crispy'
>> include Crispy
>> object = YourCoolClass.new
>> spy_into object
>> 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'
Spy methods with no arguments
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
Spy methods with arguments
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
Spy methods with arguments and a block
Sorry, I'm still thinking of the specification for that case.
Count method calls
>> 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
Get more detailed log
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
Stub Methods of a Spy
>> 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).count_received :your_lovely_method
=> 3
Spy on Instances of a Class
>> 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
>>
>> spy_of_instances(YourCoolClass::Again).received_with_receiver? instance, :your_another_method
>>
>> 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
Stub Methods of Instances of a Class
>> 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!"
Stub Methods of a Double
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."
Spy on a Double
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
Stub Constants
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!"
Configure Methods which Spy Should NOT Log
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
Embedding Crispy into your Testing Framework
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"
Contributing
- 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