Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Object#stub can now be used without a block (along with Object#unstub) - Closes #152 #153

Closed
wants to merge 1 commit into from

3 participants

@zenspider
Owner

Why?

@chancancode

I outlined my rationale in #152, but I'll give an example here (still somewhat contrived):

class Date
   def recent?
      (self - Date.today).to_i.abs <= 30
   end
end

describe Date
  before { Date.stub :today, Date.new(2012,9,1) }
  after  { Date.unstub :today }

  describe "#recent?"
    it "must return true for recent dates" do
      Date.new(2012,8,2).recent?.must_equal true
      Date.new(2012,9,15).recent?.must_equal true
      Date.new(2012,10,1).recent?.must_equal true
    end

    it "must return false for not-so-recent dates" do
      Date.new(2011,1,1).recent?.must_equal false
      Date.new(2012,8,1).recent?.must_equal false
      Date.new(2012,10,2).recent?.must_equal false
      Date.new(2014,12,31).recent?.must_equal false
    end
  end
end

This also works when say, testing IO stuff and APIs, where you can stub out things like File.open upfront in a setup/before block and restore them in the teardown/after block.

@zenspider zenspider was assigned
@eloyesp

Why not to unstub automatically on teardawn? This way, you cannot shoot yourself (because stubs are destroyed for free). in this way, a stub without a block can make a hook on after (or teardown) that remove the stub.

before { Date.stub :today, Date.new(2012,9,1) } # => after { Date.unstub :today }

This way stubing would be easier, and foolproof.

If it is possible to make an after hook (or teardawn) from inside a test it could be easier to add a stub inside a test with the same idea (and much more readable to add several stubs).

@chancancode

My guess is this is probably too much magic for @zenspider... Also, you might legitimately need to use either the stubbed or original method to do some cleanup work in your own after block, so there is this question of where this "automatic" cleanup block should be inserted into the chain.

@eloyesp

This block should run at the "very end" and should unstub every stubed method. Nothing stop to unstub manualy inside the test or in after block.

I'm thinking on a quite simple implemetation, but i'm not sure if its possible. I'd add an attr_accessor stubs (with a hash may be), so if you stub something without a block, then usefull info is added to this hash (like {:Date => :today} ), when you unstub it is deleted, and at the very_end (after_teardown) it unstub everything (if there is something to unstub).

As it is run in an ensure clause it will run for sure, so the environment keeps clean, and this will look nicer.

def test_stale_eh
  obj_under_test = Something.new

  refute obj_under_test.stale?

  Time.stub :now, Time.at(0) # stub goes away after teardown
  assert obj_under_test.stale?
end
@eloyesp

I looked at the code a while, and the tests are realy difficult to me to understand.

But is worse that I dont know how to refer to the TestCase instance from inside the test method.

@chancancode

@zenspider I came across another use case for this today:

  def with_doorkeeper_token(resource_owner_id = nil, scopes = [], &block)
    token = Object.new

    token.define_singleton_method(:accessible?) { true }
    token.define_singleton_method(:resource_owner_id) { resource_owner_id }
    token.define_singleton_method(:scopes) { scopes }

    @controller.stub(:doorkeeper_token, token) { yield }
  end

This could have been...

  def with_doorkeeper_token(resource_owner_id = nil, scopes = [], &block)
    token = Object.new

    token.stub :accessible?, true
    token.stub :resource_owner_id, resource_owner_id
    token.stub :scopes, scopes

    @controller.stub(:doorkeeper_token, token) { yield }
  end

Small difference, but the intent is clearer in the second version IMO. (I guess this might also be an argument against automatically unstubing in after/teardown.)

I picked up your suggestion (using a helper method) where it makes sense, but I still think there are use cases like these where it might make sense to do it differently, and I don't think minitest should dictate that I can't ever use it without a block (or that I must unstub a method every time for that matter).

@chancancode

In case you are wondering why I didn't use a mock, it's because these methods might get called multiple times (or not at all) and that's not the focus of my tests here to verify that.

@eloyesp

it might be done with the actual implementation... (but it's realy ugly)

def with_doorkeeper_token(resource_owner_id = nil, scopes = [], &block)
  token = Object.new

  token.stub :accessible?, true do
    token.stub :resource_owner_id, resource_owner_id do
      token.stub :scopes, scopes do
        @controller.stub(:doorkeeper_token, token) { yield }
      end
    end
  end
end
@chancancode

My point exactly (see also my comment in #152). Trust the programmer and let the programmer decide.

@zenspider
Owner

Closed for reasons spelled out in #152.

@zenspider zenspider closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 15, 2012
  1. @chancancode
This page is out of date. Refresh to see the latest.
Showing with 37 additions and 7 deletions.
  1. +18 −5 lib/minitest/mock.rb
  2. +19 −2 test/minitest/test_minitest_mock.rb
View
23 lib/minitest/mock.rb
@@ -157,10 +157,23 @@ def stub name, val_or_callable, &block
end
end
- yield self
- ensure
- metaclass.send :undef_method, name
- metaclass.send :alias_method, name, new_name
- metaclass.send :undef_method, new_name
+ if block_given?
+ begin
+ yield self
+ ensure
+ unstub name
+ end
+ end
+ end
+
+ def unstub name
+ original_name = "__minitest_stub__#{name}"
+ metaclass = class << self; self; end
+
+ if metaclass.method_defined? original_name
+ metaclass.send :undef_method, name
+ metaclass.send :alias_method, name, original_name
+ metaclass.send :undef_method, original_name
+ end
end
end
View
21 test/minitest/test_minitest_mock.rb
@@ -221,15 +221,26 @@ def teardown
end
def assert_stub val_or_callable
- @assertion_count += 1
+ @assertion_count += 2
t = Time.now.to_i
+ assert_stub_block val_or_callable
+ assert_stub_non_block val_or_callable
+
+ @tc.assert_operator Time.now.to_i, :>=, t
+ end
+
+ def assert_stub_block val_or_callable
Time.stub :now, val_or_callable do
@tc.assert_equal 42, Time.now
end
+ end
- @tc.assert_operator Time.now.to_i, :>=, t
+ def assert_stub_non_block val_or_callable
+ Time.stub :now, val_or_callable
+ @tc.assert_equal 42, Time.now
+ Time.unstub :now
end
def test_stub_value
@@ -271,4 +282,10 @@ def test_stub_yield_self
@tc.assert_equal "bar", val
end
+
+ def test_unstub_is_not_destructive
+ t = Time.now.to_i
+ Time.unstub :now
+ @tc.assert_operator Time.now.to_i, :>=, t
+ end
end
Something went wrong with that request. Please try again.