The test asserts that you call validates_uniqueness_of, but it doesn’t
actually test your logic.
So it’s useful to see if someone accidentally deletes your code, but
if you have
validates_uniqueness_of :user, :scope => :project_id
or something more complex, it won’t test this behavior.
One of the rules that I learned with TDD, that I brought with me to
BDD, is that you only specify or test what you write. Everything else
is assumed to behave as intended. This rule is, among other things,
intended to keep us productive: we can’t write a test suite for an
entire framework every time we use that framework to implement an
application. At some point, we have to trust that the frameworky bits
work as intended.
I don’t exactly assume that the code I’m depending on is well tested,
just that it’s not my job to specify its behaviour.
If you were to write an example that creates two models, and ensures
that the duplicate errored appropriately, my “rule” is violated. Each
of your examples is then specifying the behaviour of the
validates_uniqueness_of method, which you didn’t write.
Doing this with mocks ensures that you called validates_uniqueness_of,
and doesn’t test the behaviour of the method which, since you didn’t
write it, get to assume behaves as expected. This is where my “rule”
falls down: mocking the call prevents you from changing the
implementation without changing the behaviour.
That is to say that you may want to, in the future, roll your own
uniqueness validation that is called in a different way than
ActiveRecord’s, but behaves in the same way. If you mocked, you could
change the implementation without changing the behaviour, and still
have a failing example.
This is where the line is drawn for me: I need to be able to change
the implementation, and only have my examples fail when I’ve changed
the behaviour.
So, in this case, using mocks and expecting Rails’ built-in validation
is fragile. But in general, I try to stick to the “rule” (let’s say
guideline from now on) that I outlined above.
-Steven