I believe Test Fixtures in Ruby on Rails are, for the most part, evil.
They are, of course, the ubiquitous *.yml files that reside in either your spec/fixtures or test/fixtures directories. Whilst they are great for getting some test data into your database really quick, and ensuring that your test database is in a known state before each test is run, there are some very serious flaws with them. Particularly when your project team increases to more than just 1 person and/or when your application's database schema begins to grow in size.
This is the KEY reason to stay away from Test Fixtures. Use Test Fixtures for what it is intended for (adding data to be asserted and checked for in tests) will quickly result in very brittle tests. The simple reason being that the test data is shared across all tests that include that fixture. Adding, updating or deleting any entries in a yml file will inevitably break tests that depend on certain data. It is not hard to imagine breaking hundreds of tests as a result of a few simple changes to a test fixture file, especially when working with any non-trivial Rails project.
There are some techniques that I have seen to try to workaround this limitation. However, they simply do not address all possible scenarios that need to be tested. These techniques are also inevitably more verbose and involve including much more logic in test code. This is bad. There are many scenarios where, for simplicity and practicality, you will simply want to rely on the overall state of your tables. Theres no two ways about it. Test Fixtures makes your test suite brittle and workarounds make your test suite more complex.
Try using Test Fixture files to set up associations when there are many join tables involved. Any non-trivial application will have its fair share of one-to-many and many-to-many relationships. Join tables in particular are notoriously hard to setup, often requiring 3 yml files to be open and referenced at the same time (2 for each of the model table ymls involved, and 1 for the join table yml).
In contrast, saving objects through ActiveRecord's persistence framework gives the developer a very OO way to create test data. Most importantly it takes care of any join tables. It just makes so much more sense to save data like so:
Product.create!(:brands => [brand_one, brand_two],
:retailers => [retailer_one, retailer_two])
rather than through the cumbersome yml files.
The Fixtures that a test depends on are typically declared at the beginning of a test, such as:
fixtures :products, :retailers
This ensures that the appropriate YML files are run, and the data in those tables are in a known state. However, forgetting to include a test fixture for a table that the test depends on (in this case the join table between products and retailers, conventionally named products_retailers) will result in the join table being in an unknown state. The above line should really as such:
fixtures :products, :retailers, :products_retailers
This means that whilst the test may pass because the join table happens to be in a particular state, it will probably fail on someone else's box. This is very dodgy.
A naive solution would be to always run all fixtures in the 'fixtures' directory before each test. However, this is crap when running controller/view/helper tests, as these tests should really not be touching the database. Instead, a more appropriate solution is to create a method 'use_fixtures' which is accessible from any test run and which, when called, will run through all fixtures in the 'fixtures' directory and execute them to setup the test database. This allows model/integration tests to use fixtures simply by declaring 'use_fixtures' as such:
describe Product do
use_fixtures
it 'should do something' do
...
end
end
Thus, any tests which are run against a database will have all fixture files executed, and any tests which are not run against the database simply do not need to include the 'use_fixtures' line. A good place to put the 'use_fixtures' method is in the spec_helper.rb file (when using RSpec).
I believe Test Fixtures have a role to play in test suites, but should be used selectively. Following are some of the ways in which I would use Test Fixtures:
Such an example is saving an administrator user, perhaps with an administrator role. This sort of data will always be useful for test runs, and it is quite ok for all tests to depend on a user with the login 'admin' and password 'password' to be present in the system.
However, in the large majority of cases, I would leave test fixtures empty and use the test fixtures framework simply to ensure that the database tables are in a known state (mostly empty, except for the odd exception such as administrator user).
This is my preferred way to create data. Sure, it may involve a bit more repitition (need to write the code to save new objects each time) but thats what the before(:each) method is there for! The huge advantage is that your test code is simplified because it knows the current state of the database and can thus create explicit assertions. Also, test code is no longer brittle. Changing test data setup in the before(:each) method will only affect all tests run within the scope of that before(:each) and not other tests.
It maybe slightly more repetitive, but I believe that the advantage (no brittle tests and OO way of saving data) far outweighs the repetition involved, especially when your team or code base increases.
Complex relationships with many associations, such as in the case of products, can be quite verbose to setup in code (have to save all associations as well as the product itself). Thats where a builder pattern, such as described here:
http://geekswithblogs.net/Podwysocki/archive/2008/01/08/118362.aspx
This allows you to quickly create a default product, whilst giving the flexibility of overwriting attributes to suit your particular test.
Comments ...
but I hadn't seen someone explain as clearly "why", before coming
here.