[rspec-users] have_one and have_present

nicholas a. evans nick at ekenosen.net
Mon May 7 12:44:40 EDT 2007

On 5/5/07, David Chelimsky <dchelimsky at gmail.com> wrote:
> This is a very gray area.

Agreed.  ;-)

> Why do we care about avoiding implementation details in tests?
> Because they make tests brittle.

I think that there is another reason, which is sometimes more
important (and sometimes less): doing this communicates the actual
system requirements better than do implementation details.

> Are we going to possibly change from ActiveRecord::Base to
> some other persistence framework?

No.  However, just for the sake of argument, I will bring up that I
have recently had to change my persistence framework from
ActiveRecord::Base (version 1.1.6) to ActiveRecord::Base (version
1.2.2).  Various other incompatibilities (largely brought on ourselves
by working too closely with private APIs) are still fresh in my mind.
But I agree with your point here; the core validations aren't likely
to be changing much from one version of AR::Base to the next.

> They cover a LOT of ground in a very clear way.

I still have misgivings about just how clear it actually is.

> Here's another way to look at it. There is a rule of thumb in TDD that
> says "test YOUR code, not somebody else's."
> Accepting that approach, then what is the difference, really, between
> that and Jay's approach, which essentially mocks AR and lets you
> specify that your model class interacts with it correctly?

I usually come down rather heavily in the "isolation test with all
dependencies mocked out" camp.  I'm really not sure how to
consistently resolve my double standard here.  I simply feel very
uneasy using mock expectations for model validations.

on readability:

Granted, "validates_presence_of" is incredibly simple and easy for
anyone to understand and validates_numericality_of and
validates_confirmation_of may speak to us as rails programmers, but do
they speak to our BAs and PMs?  "Numericality" isn't even an english
word!  :-)  Wouldn't it be much simpler and far more communicative if
we said, "when the object's attributes are set to these values, the
object is marked as invalid, can't be saved, and has this error
message"?  From a business user's perspective, isn't that what they
really care about?

on changing the implementation after the fact (and brittle tests):

I think that most apps don't need to use much beyond the basic
rails-provided validations, so this approach probably works better for
them than for my company.  But my apps have relatively few
validates_presence_of, compared to a large number of state based (or
other complicated) validations (when this attribute is set to such and
such, or when the model is in such and such a state, then these other
conditions must be valid").  When we do use the standard rails
validations, we often also pass in an :if or :unless block.  The
further you stray from the simple use case of just adding the
validation to the model with no extra parameters, the more it looks
like you are just copying and pasting code from your specs into your
production code.  (Unless you aren't working TDD/BDD, in which case
the copying goes in the opposite direction!)  If you were to use
valid/invalid examples, then you wouldn't need to change each example
every time you add another condition to your validations... you would
simply add/modify/remove the examples that make sense.  I would argue
that this makes more sense from a TDD/BDD perspective, as well as from
a "brittle tests" perspective.

on meaningful specs:

validates_format_of (or other similarly complicated validations, such
as using :if or :unless) is really where I have my biggest problem.
Ignore all of the above as an misguided ideological rant, and I'll be
completely fine with that.  In time, I might even agree with you.  ;-)

But what does the following mean:

model.should validate_format_of(:email, /\w+@\w+\.\w+/)

oh, that was rather naive, it isn't catching "foo bar blatz at test.com"
as invalid.

Okay, let's change the code and the spec:

model.should validate_format_of(:email, /^\w+@\w+\.\w+$/)

oh, now we have one of my pet peeves, "foo+bar at test.com" isn't
considered valid, nor is "foo at test.domain.com".  "foo_bar at test.com" is
still valid, but "foo-bar at test.com" is not.

Do you see my point?  These are very simple regular expressions, but
they are already non-trivial and less obvious to read than what they
are able accomplish.  Sure, I could go onto the net and borrow someone
else's pre-built email regex validation... but that only helps with
email addresses.  What about some other complicated problem more in
your specific domain?  What about a problem that you originally
*thought* you could solve with a regex, but you later found something
that a regex couldn't handle or you found a more performant or a
simpler implementation.  That we are calling "validates_format_of"
with a specific regular expression has almost *nothing* to do with our
requirements.  That we consider certain classes of strings to be valid
and certain to be invalid, *that* is what matters.

My fear is that by adopting this style of validations, we make things
a little bit more concise where we need it the least (with the very
very simple validations), but encourage a style that will be
detrimental for the even the mildly complicated validations.

Here's another attempt at a syntax that is hopefully simple and readable:

valid_model.should validate(:with_message => "Unacceptable email address.") do
  valid :email => "foo at bar.com"
  valid :email => "foo+extension at bar.com"
  valid :email => "foo-bar at test.com"
  valid :email => "foo-bar at test.domain.com"
  invalid :email => "no spaces please foo at bar.com"
  invalid :email => "@no.username.com"
  invalid :email => "only_tld at com"
  invalid({:email => ""}, :with_message => "Should provide email address")
  valid :email => "", :status => "registration incomplete"
  valid :email => "", :user_role => "fake"
  valid :email => "", :user_role => "admin"

Keeping in mind, of course, that real business rules tend to be even
crazier than a simple email address validation.  I'm not even saying
that all of the above rules above make sense, but that doesn't mean
that rules of that level of complexity won't be demanded by our
customers.  ;-)  I think that the above style communicates much more
clearly than:

model.should validate_format_of(cryptic_regex).with(:option1 =>
"blahblahblah", :option2 => "blahblahblah")

But again, I might be tilting at windmills, so I'm looking for some
validation of my thoughts.  ;-)  (pun unfortunately intended)


More information about the rspec-users mailing list