The classic behavior mocking dilemma
We would like our tests to not inappropriately cross over layers of the system (such as accessing the database), and we would like our tests to not be dependent on implementation. Unfortunately, these two concerns are often at odds with one another.
Let's say you are testing this method:
class Foo
def bar
Bar.find(bar_id)
end
end
There are two ways you could test it. One doesn't cross over into the database:
describe "#bar" do
let(:foo){FactoryGirl.build_stubbed(:foo)}
let(:bar_id){double("bar_id")}
let(:bar){double("bar")}
subject{foo.bar}
before do
foo.stub(:bar_id){bar_id}
Foo.stub(:find).with(bar_id){bar}
end
it{should eq bar}
end
Problem: if the implementation is changed to Foo.where(id: foo_id).first
, the spec will break even though the behavior is still correct.
The other way is like so:
describe "#bar" do
let(:bar){FactoryGirl.create(:bar)}
let(:foo){FactoryGirl.create(:foo, bar_id: bar.id)}
subject{foo.bar}
it{should eq bar}
end
In this case, we always test that the method retrieves the correct object from the system, somehow. The implementation can be changed any number of ways and the test will still pass and still be testing for the appropriate behavior.
Are there any other approaches to this problem? Perhaps having this problem in the first place is a code smell?