I tend to have the majority of my tests at the model level, with a much smaller number at the feature level covering the main features of the app. I’ll typically have only a handful in between (controller, view, helper). Code climate has a great blog post about The Rails Testing Pyramid that describes a similar structure.
I will typically start with the feature spec and use it to drive things out for as long as it is giving me meaningful feedback. Say I were building a users index that needed to display a list of users with a particular bit of data about them, then I would begin with a feature spec and run it, following the failure messages:
- “no route matches /users” clearly tells me to add the needed route.
- “missing file UsersController” tells me to create the controller.
- “missing action #index in UsersController” tells me to write the action
- “missing template” tells me to create the template
I might be able to push a bit farther based on the desired content of the template, but at some point I’ll feel that the feature spec’s failures are no longer giving me useful feedback that is driving the next bit of code I need to write. When that happens, I’ll typically drop down to a unit / model spec to add drive out the behavior I need on my User model. When that is done I will pop back up to the feature spec and in theory it should now be passing as well.
I use controller specs sparingly when there is unique logic in the controller. In general I try to avoid this (significant logic in the controller), instead opting to extract a class that contains the logic and have the controller collaborate with that object, but some things do belong in the controller. An example would be unique authorization or access control. I will typically write control specs to drive out that sort of behavior. This is most common when I am working on any sort of API.
View specs I use sparingly if there is logic that exists mostly in the view but is hard to test from a different level. Again, I think this is not a great sign, and would probably want to refactor to pull that logic out into an object or at least a view helper (which I would test independently), but it does come up.
100% coverage is certainly not a bad thing, but I rarely find myself chasing it as a goal in and of itself. TDD will get you to the important 90%. Further, I would trust my intuition far above any coverage number. I’ve seen situations where I had close to 100% coverage, but didn’t feel confident that the tests covered everything. Similarly, I’ve had well less than 100% coverage but felt confident in the code.