[rspec-users] Mocking: brittle specs and tight coupling?

Pat Maddox pat.maddox at gmail.com
Sat Apr 11 19:24:50 EDT 2009


On Sat, Apr 11, 2009 at 3:59 PM, Brandon Olivares
<programmer2188 at gmail.com> wrote:
> Hi,
>
> I've read of complaints that mocking can make the specs very brittle, in
> that if the method is changed at all, it will break the specs, even if the
> behavior remains the same. Do you find this to be the case? How do you deal
> with this if so?

http://patmaddox.com/blog/you-probably-dont-get-mocks


> Also, when I used to do TDD in PHP, well there wasn't the ability to modify
> the class on the fly like in Ruby, so you actually had to do dependency
> injection, but people generally looked at this as a good thing, for loose
> coupling. So if a controller method for instance used a User model, then it
> would be preferred to get that instance from somewhere, either passing it to
> the method itself or calling another method to instantiate it.
>
> I notice this isn't a concern in RSpec or in Ruby in general. Do you view
> this differently, or what is the preferred way to deal with dependencies? Is
> it fine just to do:
>
> User.should_receive(:new).and_return(User.new)
>
> Just as a very simple example?

I have an example of this in my Legacy Rails talk and say it's the
sort of thing that would make a Java programmer run for the fucking
hills.  That's not entirely true because there are a couple mock
frameworks that do let you do that, but in general they prefer to
avoid it because it requires bytecode manipulation.

Ruby is much more flexible and gives us a couple ways of injecting
dependencies.  You've got traditional DI:

class Order
  def calculate_tax(calculator)
    calculator.calculate total, us_state
  end
end

You've got traditional DI + default args

class Order
  def calculate_tax(calculator=TaxCalculator.new)
    calculator.calculate total, us_state
  end
end

You can partially mock on the target object:

class Order
  def calculate_tax
    calculator.calculate total, us_state
  end

  def calculator
    TaxCalculator
  end
end

order = Order.new
order.stub!(:calculator).and_return(mock('calculator', :calculate => 1.25))

or you can use partial mocks somewhere inside of the target object,
like you showed.

The pattern you showed is popular because often you won't ever want to
pass in a different object.  In your UsersController you're only ever
going to deal with the User class as a repository, and if you change
it then it's a fairly big change and you don't mind updating your
tests.

I find that you can use mocks to express the intent of the class well.
 Don't use constructor/setter/method dependency injection if you don't
need it...accept a bit tighter coupling and use partial mocking if all
you're trying to do is isolate behaviors.

Pat


More information about the rspec-users mailing list