{"id":25,"date":"2007-05-23T12:38:54","date_gmt":"2007-05-23T18:38:54","guid":{"rendered":"http:\/\/www.pervasivecode.com\/blog\/2007\/05\/23\/first-rails-gotcha-rails-doesnt-tear-down-fixture-data\/"},"modified":"2007-07-11T13:27:25","modified_gmt":"2007-07-11T19:27:25","slug":"first-rails-gotcha-rails-doesnt-tear-down-fixture-data","status":"publish","type":"post","link":"http:\/\/www.pervasivecode.com\/blog\/2007\/05\/23\/first-rails-gotcha-rails-doesnt-tear-down-fixture-data\/","title":{"rendered":"First Rails gotcha: Rails doesn&#8217;t tear down fixture data"},"content":{"rendered":"<p>Rails doesn&#8217;t delete loaded fixtures in the tear down phase of tests, but it does delete and re-insert the fixtures you do use at the beginning of tests. This is a serious problem if you use foreign keys.<\/p>\n<p>This issue is covered in the Rails issue tracker here: <a href=\"http:\/\/dev.rubyonrails.org\/ticket\/2404\">http:\/\/dev.rubyonrails.org\/ticket\/2404<\/a><br \/>\n<!--more--><br \/>\nFixtures are YAML-format text files that hold test data. Your test cases can ask for fixtures to be loaded, and Rails will take care of putting them in the database during your tests&#8217; setup phase.<\/p>\n<p>Sadly, at no point are fixtures removed. Subsequent tests may reload the same fixtures (to assure that they&#8217;re intact), even if &#8220;transactional fixtures&#8221; (which really means transactional tests) are enabled. This reloading is slightly sloppy from a performance standpoint, but it&#8217;s a showstopper if you have foreign keys enabled, because the whole test class will fail.<\/p>\n<p>Here&#8217;s an example: class A is referenced by B and C. The test for class B uses fixtures for A, B. The test for class C uses fixtures for A, C. When the fixtures for class C&#8217;s test are loaded, Rails does <code>delete from A<\/code> and then reloads the fixtures for A, except the delete statement fails because there is still B fixture data referencing it. This data has nothing to do with the C test case&#8217;s needs, but it breaks the C test case.<\/p>\n<p>Alternatives to solve this include:<\/p>\n<ul>\n<li>putting a <code>fixtures<\/code> line in test_helper.rb that loads all of your fixtures (so this will be included in every test class). The old data will be deleted and reloaded in the correct order so FKs aren&#8217;t violated, but lots of unnecessary data will be loaded for every test class&#8230; sloooow.<\/li>\n<li>changing all of your foreign keys to <code>:deferrable => true<\/code>, so that the code in the big transaction that gets rolled back at the end of each test class never notices that the FKs were violated. This would be tedious to do and intrusive to your nice pretty migration code. Worse, it largely defeats the purpose of FKs, since your nice comprehensive test suite effectively runs with FKs disabled.<\/li>\n<li>Not using fixtures, and using something else to load your test data. That might include explicit test data creation in your code (which is a pattern that fixtures are supposed to be a tidy implementation of), or a big SQL file full of insert statments that you execute at the beginning of your test suite.<\/li>\n<li>Monkeypatching Rails so that all fixtures that have been loaded are deleted when the test class has finished running. I don&#8217;t know how this would work; there might not be an easy hook to accomplish this. Also deleting and reloading the same fixture over and over if different test classes need it would be extra work that could theoretically be avoided.<\/li>\n<li>Monkeypatching Rails so that once a fixture is loaded, it is left alone (except for the beginning of the test suite when the database is dropped and recreated empty, as usual). This only works if &#8220;transactional fixtures&#8221; are enabled, since the test data is rolled back to its original loaded state at the end of each test. Since this is the default Rails 1.0+ behavior, it&#8217;s not a stretch to assume that this will usually work. The downside is that if you&#8217;re expecting that the database will be totally empty except for the fixtures you specifically asked for, you&#8217;ll be surprised by the fact that there&#8217;s other data in there. (But this is the current situation anyway; boy was I surprised!) To work around that minor issue, you could just make sure there are some records without dependent rows, so that regardless of whether the dependent table is loaded or not, the test expecting not to find dependent rows will pass.<\/li>\n<\/ul>\n<p>This last option is the one that several people commenting on the bug suggested. Here&#8217;s the code I used, based on <a href=\"http:\/\/dev.rubyonrails.org\/ticket\/2404#comment:18\">this comment<\/a> from that bug.<\/p>\n<pre>\r\nclass Fixture\r\n  attr_reader :class_name\r\nend \r\nclass Fixtures\r\n    @@inserted_fixture_list = {}\r\n    alias original_insert_fixtures insert_fixtures\r\n    def insert_fixtures\r\n        return if 0 == values.size\r\n        return if values.size > 0 && @@inserted_fixture_list[values[0].class_name]\r\n        @@inserted_fixture_list[values[0].class_name] = true\r\n        class_name = values[0].class_name\r\n        if class_name.respond_to? \"constantize\"\r\n            table_name = values[0].class_name.constantize.table_name\r\n        else\r\n            table_name = values[0].class_name.table_name\r\n        end\r\n        unless ActiveRecord::Base.connection.select_one(\"select 1 from #{table_name}\")\r\n            original_insert_fixtures\r\n        end\r\n    end\r\n    def delete_existing_fixtures() end\r\nend<\/pre>\n<p>It fixed the problem for me. I think that in order to avoid unexpected problems with fixture data being loaded when you didn&#8217;t ask for it in the current test, I may just preload all the fixtures in test_helper.rb anyway, and let the patch above be a performance optimization (since it would notice that the data was already there and not bother reloading it for subsequent tests).<\/p>\n<p>Hope this helps!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Rails doesn&#8217;t delete loaded fixtures in the tear down phase of tests, but it does delete and re-insert the fixtures you do use at the beginning of tests. This is a serious problem if you use foreign keys. This issue is covered in the Rails issue tracker here: http:\/\/dev.rubyonrails.org\/ticket\/2404<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26,20,30],"tags":[],"class_list":["post-25","post","type-post","status-publish","format-standard","hentry","category-ruby","category-ruby-on-rails","category-testing"],"_links":{"self":[{"href":"http:\/\/www.pervasivecode.com\/blog\/wp-json\/wp\/v2\/posts\/25"}],"collection":[{"href":"http:\/\/www.pervasivecode.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.pervasivecode.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.pervasivecode.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/www.pervasivecode.com\/blog\/wp-json\/wp\/v2\/comments?post=25"}],"version-history":[{"count":0,"href":"http:\/\/www.pervasivecode.com\/blog\/wp-json\/wp\/v2\/posts\/25\/revisions"}],"wp:attachment":[{"href":"http:\/\/www.pervasivecode.com\/blog\/wp-json\/wp\/v2\/media?parent=25"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.pervasivecode.com\/blog\/wp-json\/wp\/v2\/categories?post=25"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.pervasivecode.com\/blog\/wp-json\/wp\/v2\/tags?post=25"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}