Rails testing miniseries part 4: Model testing
In my experience, model testing has by far the highest payoff of bugs squashed per minute or line of test code written.
I hear some people saying their models are so simple they don't need tests. They're frequently surprised what issues later turn up, but in any case, if there's next-to-nothing in your models, it will take hardly any time to write full test coverage for them!
(Also, if your models are really simple that often means you've plonked your model code in the controllers, which aside from being bad practice in general makes it very different to test effectively, but we'll come back to that issue.)
Tools
For Rails model testing is covered by “unit tests” in Rails' built-in Test::Unit functionality, or by RSpec “model specs”.
(For programmers coming from other languages, “unit tests” here is misleading – usually unit tests are simply tests for a component of other kind. In the Rails Test::Unit world, unit tests refer specifically to model tests.)
Check out the previous article for more on Test::Unit and RSpec.
Some things to test for
- Attributes – protected, defaults (both pretty straight forward, so often unnecessary)
- Associations – in particular, any that have conditions or extensions set
- Callbacks – that they set attributes as they should, that they create related objects correctly, that don't overwrite the actual attribute changes you've made (especially on new objects)… and that they don't throw
- State model transitions – that your go_public and file_lawsuit methods do what they should
- Custom model methods – state inquiries (is_deadbeat?, really_popular?), calculators (average_profit, estimated_completion_date) and special finders (most_commented, hottest_person_at_the_gym) etc.
- SQL – that the SQL server doesn’t barf… (you'd be surprised)
Points to remember
- Messy/complex/subtle code is more likely to contain bugs, so test what you think is important to test – those are just some suggestions to get you started.
- Don't retest boring ActiveRecord code (test what you do with Rails, don't repeat Rails' test coverage; focus on complex interactions with it and other likely problem areas, not the basics of ActiveRecord)
SQL issues
About that “SQL server doesn't barf” thing. There is a lot of noise at the moment about testing without hitting the DB, for speed. I agree, speed is very good and important since it means short iterations, and for most of your tests, you aren't interested in testing the repetitive DB CRUD operations, so bypassing is often worthwhile.
You can, for example, test out most calculators and state inquiries and so on with mocked attributes only.
But if you ever do any database stuff other than trivial CRUD, you must let the tests for at least those particular bits of code actually go to the DB.
I couldn't count the number of times I've seen problems with things like joins (especially scoped joins) and includes – for example, when someone's added an extra join or include and now there's two attributes in the select with the same name, so all the attribute names in the condition strings etc. need to be fully-qualified… All simple to fix, but they need to be tested for.
For any nontrivial model query or update statement, if you stub/mock out the database interaction, you're stubbing out the main thing that needs to be tested. 'Nuff said.
Test database servers
And anyone who thinks you can run against one database (eg. mysql or postgres) but test against something faster (eg. in-memory sqlite) needs their head examined.
Coming up next
Next in this series:
Controller testing.