igrep / crispy

Test Spy for Any Object in Ruby.
http://rubygems.org/gems/crispy
MIT License
22 stars 2 forks source link

Crispy

Code Climate Test Coverage Build Status Gem Version

Test Spy for Any Object in Ruby.

Features

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 # 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'

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)` keeps its spied log of a method even after stubbing the method.
>> 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
>> # 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

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

  1. Fork it ( https://github.com/igrep/crispy/fork )
  2. Create your feature branch (git checkout -b your-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin your-new-feature)
  5. Create a new Pull Request