Sometimes fakes are a good choice. But the price is high. In particular, they make changing code harder. You rename a method, but all tests that stub the previous version keep passing. It would be nice if those started to fail so that when they're green again, you can be certain that everything that had to be updated was updated. Well, now you can.
To be fair, you already can in Rspec with their verifying double. But what about Minitest? And there are still differences. Unlike verifying double:
- here you need to supply real objects to create fakes. That's controversial - as it goes against the idea of testing in isolation - but realistically, at least in Rails, everything is loaded anyway, so that's a moot point;
- strictly_fake is aware of autogenerated methods (e.g. ActiveRecord model accessors);
- strictly_fake does not stub constants (e.g. classes). You can, however, pass a fake to Minitest's
Object#stub
to achieve this (see example below); - strictly_fake performs a full parameter check, comparing required, optional and keyword arguments (veryfing double only checks arity afaik).
Add this line to your application's Gemfile:
gem 'strictly_fake'
And then execute:
$ bundle install
require 'strictly_fake'
# Given we don't want to actually pay in tests
class PaymentGateway
def pay(recipient, amount, reference = nil);
# calls remote payment provider...
end
end
# We can use a fake instead
payment_gateway = StrictlyFake.new(PaymentGateway.new)
# Let's stub a method that _isn't_ defined in PaymentGateway:
payment_gateway.stub(:bar)
# => throws "Can't stub non-existent method PaymentGateway#bar (StrictlyFake::Error)"
# Let's stub an existing method, but with a wrong signature:
payment_gateway.stub(:pay) { |amount| }
# => throws "Expected PaymentGateway#pay stub to accept (req, req, opt=), but was (req) (StrictlyFake::Error)"
# All good now!
payment_gateway.stub(:pay) { |recipient, amount, reference = nil| 'Success!' }
payment_gateway.pay('Dave', 10)
# => 'Success!'
# Makeshift mock
invoked = false
payment_gateway.stub(:pay) do |recipient, amount, reference = nil|
# Further arguments check
assert(amount.is_a?(Money))
invoked = true
end
payment_gateway.pay('Dave', Money.new(10))
assert(invoked)
# => Pass
# Stubbing class methods is no different
time = StrictlyFake.new(Time)
time.stub(:now) { 'XYZ' }
time.now
# => 'XYZ'
# Combine with Minitest stub to actually stub constant
Time.stub :now, time.now do
Time.now
# => 'XYZ'
end
Note: Minitest is required for assert*
to work.
After checking out the repo, run bundle install
to install dependencies. Then, run bundle exec rspec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/featurist/strictly_fake.
The gem is available as open source under the terms of the MIT License.