5 ms·
There's a school of thought which says that unit tests should never touch a database. This has always struck me as a strange way to write tests: Mocking out a d
by ekidd 15y ago
There's a school of thought which says that unit tests should never touch a database. This has always struck me as a strange way to write tests: Mocking out a database is quite painful, and it means that you're testing an artificial system very different from what you run in practice. It's much easier just to unit test your model classes against SQLite, which is blazingly fast.
Similarly, some Rails developers try to completely isolate their models, controllers and views from each other when unit testing. A controller test, for example, won't render any views, and it will mock out the model layer. Similarly, a view test will ignore the controller, render the view manually, and mock out the model layer.
In my experience, these kinds of fully-isolated unit tests take a fair bit of work to set up, and they result in overspecified module boundaries. Plus, there's always the risk that your view objects will work fine using mocks, but fail using real model objects.
Personally, I prefer to test my Rails model objects using SQLite. And increasingly, I sharply minimize the amount of code in my controllers (and views, of course), and test it almost entirely using integration tests in a simulated browser. This makes it easy to get 100% code coverage with very little work, and it's still quite fast for a medium-sized app.
I mock web services using the 'vcr' gem, which can record a series of HTTP responses and play them back later. And of course, I mock out credit card gateways, AWS API calls, and anything else which requires a network to run. But I don't use mocks just for the sake of using mocks.
- snitko 15y agoYes. And this way a controller test sometimes catches an unspeced model bug. Happens often enough to me to not mock everything.
- wccrawford 15y agoActually, Unit tests should only test 1 thing. (Hence the name 'unit' tests.) So they shouldn't be testing both your models and your logic at the same time. Plus, they are -way- faster when the database stuff is mocked out. Of course, that's not always possible and efficient.
- ekidd 15y agoMy philosophy is slightly different: Unit tests should test one API. But whenever possible, they should respect the encapsulation of that API, and only test the public access points. In particular, I dislike unit tests which dig around in the private implementation details of a module, and specify how it connects to other modules "behind" it. Invasive unit tests make it much harder to change those implementation details.
- wccrawford 15y agoThat's a good point. It's generally considered to be bad form to lock down the internals too much. It makes refactoring harder. In general, when testing a class I obey the spirit of 'testing 1 thing' by writing tests that gradually increase in scope, so the first tests are testing 1 thing, the next tests are testing 2 (1 of which was already tested) etc.
- ezyang 15y agoYou're not really talking about unit tests, then: you're talking about progressive integration tests, since the failure of a module "behind" your module can cause extra spurious failures. Now, it may in fact be the case that you may argue that you can replace unit tests with all of these sorts of integration tests. I'm not so sure: digging around the private implementation details ways usually the easiest way to test strange error conditions—but it’s by no means easy. Though these days, I try to write as much pure code as possible, nullifying the benefits of an approach like that.