January 07, 2009

Test Or Die: Validates Uniqueness Of

In the test or die, I showed a simple example of how to test validates_presence_of. Let’s build on that by adding categories and then ensure that categories have a unique name that is not case sensitive. If you haven’t been following along and want to, go back to the beginning and create your app and first test. Let’s start by running the following commands to create the category model and migration, migrate your development database and prepare your test one.

The important thing to remember when testing uniqueness of is that it does a check with the database to see if the record is unique or not. This means you need to have a record in the database to verify that the validation does in fact get triggered. You can do this several ways but since we are staying simple, we’ll do this in test/unit/category_test.rb:

This breaks the one assertion per test that some people hold dear, but we are just learning right now. As you do this more and more, you will run into gotchas but by then you will know where to look for solutions. Now run rake to see if your test is failing.

Now that we have failed, let’s add the validation to our Category model.

class Category < ActiveRecord::Base
validates_uniqueness_of :name
end

Run rake again and you will see happiness! Another way you could test the same thing above is by creating a fixture with a name of Ruby and then just use that fixture in place of cat1. The fixture file (test/fixtures/categories.yml) would look like this:

Note the bold line above. It uses the categories method to access the category fixture named ruby. It returns a category just like Category.find with an id would. The difference is that we don’t define id’s in our fixtures, so we don’t know what ruby’s id would be and, more importantly, names are often more intent revealing (think fixture names like active, inactive, published, not_published).

That was easy but we didn’t actually verify case insensitivity. Let’s add a bit more to make sure that is in fact the case.

Yep, we are good to go. So how would you test this with a scope on the uniqueness validation? Well, I’m not going to add a column and all that but it would look something like this if the column you were scoping uniqueness to was site_id.

That was pretty much off the top of my head, but it should give you the idea. The important thing is to test both sides. Don’t simply test that it is invalid for the same site, also test that it is valid for different sites. It may not be super important in this instance, but it is important to get into this mindset when testing.

You always want to think about the bounds. Test out of bounds on both sides and then in bounds to make sure that all cases are covered. Again, this is just the basics. There are other ways to do this, but I just thought I would get you pointed in the right direction.

Don’t forget to set a unique index on any field(s) you have validates_uniqueness_of. Without a unique index in your database, you aren’t able to ensure true uniqueness, due to a race condition between checking uniqueness and saving your model.

In practice, do you write out these sorts of tests over and over for each model, or do you abstract them out into methods. Seems like it should only take one line of code to do a check for common things like uniqueness checking a field/scoped field. Although at that point I start getting the creeping “writing the same code twice” feeling

@grosser – See at that point I start thinking it’s a violation of DRY to have that and the validates_uniqueness_of in the model – it seems to obviously be the same code just expressed in a different domain language

I wonder if you could dynamically translate a certain subset of common unit tests directly into application code – e.g. a plugin at runtime to read test code and make appropriate modifications to the application’s classes. Well, actually I’m almost sure you could… I wonder if you should?

These strategies are actually testing ActiveRecord, though, aren’t they? What I generally try to test is the piece that I’m responsible for, which in this case is just that the validation macro is entered correctly into the AR class definition.

I like that approach, but is it getting too purist for it’s own good? If we take one of the goals of testing to be telling us when the app fails, even if its a failure in the DB schema or in Rails(because of an upgrade)

Seems like a tough question… there’s advantages and disadvantages to either path

@crayz: That is one of the goals of testing as a whole, but it’s not (my|the) goal for unit testing. I look for application-level failures due to the framework at the functional and/or integration levels.

I’ll reiterate. On a regular basis, I do not test this way. I believe there is a natural progression that you go through when learning testing. You start with stuff like this. Then, you notice that you can make it easier on yourself and you start to create macros to test repetitive things.

The thing that I have noticed when teaching people testing though is they have to learn this first. If they start with the macros, mocking and stubbing, they never quite understand the underpinnings. Also, when they start new project, I often watch them stumble, trying to do things with all the macros, but not having their test environment setup. I think it is good for people to see that they are repeating themselves and think, well, how can I fix this? I kind of think it is good to feel the repetition pain as it helps people understand why we switch to macros and start to stub and mock things so that you don’t require hitting the database or using fixtures.

@Ben – The only thought on the other side of the argument I can come up with, because I mostly agree with you, is what if Rails changes underlying functionality that makes sense for Rails, but not for the business logic of your application.

Let’s say you were using validates_uniqueness_of :name without the :case_sensitive option and Rails changes the default from true to false. I would want to know that in my tests as a simple upgrade of Rails would modify my business logic and cause nothing to fail using your methods. Does that make more sense? Again, I agree with you, just mentioning the other side of the coin.

Also, I much like RSpec’s folder naming of controllers and models, as I think that Rails functional and unit are kind of misleading. I see Rail’s unit tests as model tests, not pure unit tests.

@crayz – I definitely do make macros. I even make TextMate bundles on top of those macros to shorten things up even more.

All that said, I’m glad everyone is commenting like this. It is good for people to realize that what I am showing is the start and that there are better ways, but sometimes you are still better of starting from the beginning. Hope this comment clears some stuff up. :)