Bob Silva posted Testing Gotchas in Rails in mid-October. I had fallen across this error myself a few times.

Well, today I had a huge code base I needed to check. I wrote the following Rake task:

lib/tasks/find_missing_fixtures.rake

1 # Find missing fixtures declarations from Rails tests
2 # Code by Francois Beausoleil (francois@teksol.info)
3 # Released in the public domain. Do as you wish.
4 desc "Finds missing fixtures declarations from your tests"
5 task :find_missing_fixtures do
6 state = :find_test_case
7 test_case = nil
8
9 Dir[test//_test.rb].each do |file|
10 File.open(file, r) do |f|
11 f.each do |line|
12 case state
13 when :find_fixture
14 case line
15 when /def test_/
16 printf "%s: %s\n", file, test_case
17 state = :find_test_case
18 when /fixtures/
19 state = :find_test_case
20 when /class (\w) < Test::Unit::TestCase$/
21 test_case = $1
22 state = :find_test_case
23 end
24
25 when :find_test_case
26 case line
27 when /class (\w) < Test::Unit::TestCase$/
28 test_case = $1
29 state = :find_fixture
30 end
31 end
32 end
33 end
34 end
35 end

Run it like this:


1 $ rake find_missing_fixtures
2 (in D:/wwwroot/wpul.staging.teksol.info)
3 test/unit/name_test.rb: NameTest
4 test/unit/application_helper_test.rb: ApplicationHelperTest
5 test/unit/application_helper_test.rb: ApplicationHelperTruncationTest

Why do I report both the file and TestCase name ? Because I put more than one TestCase per test file, as I reported in Test fixtures and behavioral testing

Of course, if I could do away with fixtures altogether…

These days, I use Thoughtbot’s Shoulda to do the same thing. The nice thing about that is that we still only have one test case per file, allowing autotest to do it’s automagical file resolution.

Yesterday, I was coding a new set of tests for a Picture class. I wanted to introduce an on-disk cache, when the picture is public.

I already had an existing test/unit/picture_test.rb, which I extended.

The tests looked like this:

test/unit/picture_test.rb

1 class PictureTest < Test::Unit::TestCase
2 def test_picture_exists_on_disk
3 pict = uploaded_file(large.jpg, image/jpeg, somepicture.jpg)
4 picture</span> = <span class="co">Picture</span>.build(pict) <span class="no"> <strong>5</strong></span> <span class="iv">picture.public = true
6 assert_model_saved picture</span> <span class="no"> 7</span> <span class="no"> 8</span> assert <span class="co">File</span>.exist?(<span class="iv">picture.cache_filename), file copied to disk
9 end
10
11 def test_thumbnail_exists_on_disk
12 pict = uploaded_file(large.jpg, image/jpeg, somepicture.jpg)
13 picture</span> = <span class="co">Picture</span>.build(pict) <span class="no">14</span> <span class="iv">picture.public = true
15 assert_model_saved picture</span> <span class="no">16</span> <span class="no">17</span> assert <span class="co">File</span>.exist?(<span class="iv">picture.cache_thumbnail), file copied to disk
18 end
19 end

I knew this was the wrong way to go – the fixture was repeated more than once. Then, I realized that what I wanted was a new TestCase. I had already reached for my mouse to hit New File when I had a flash: what prevents me from simply adding a new class to picture_test.rb ?

test/unit/picture_test.rb

1 class PictureTest < Test::Unit::TestCase
2 def test_read_from_stringio
3
4 end
5 end
6
7 class PublicPictureTest < Test::Unit::TestCase
8 def setup
9 pict = uploaded_file(large.jpg, image/jpeg, somepicture.jpg)
10 picture</span> = <span class="co">Picture</span>.build(pict) <span class="no">11</span> <span class="iv">picture.public = true
12 assert_model_saved picture</span> <span class="no">13</span> <span class="r">end</span> <span class="no">14</span> <span class="no"><strong>15</strong></span> <span class="r">def</span> <span class="fu">test_picture_exists_on_disk</span> <span class="no">16</span> assert <span class="co">File</span>.exist?(<span class="iv">picture.cache_filename), file copied to disk
17 end
18
19 def test_thumbnail_exists_on_disk
20 assert File.exist?(picture</span>.cache_thumbnail), <span class="s"><span class="dl">'</span><span class="k">thumbnail copied to disk</span><span class="dl">'</span></span> <span class="no">21</span> <span class="r">end</span> <span class="no">22</span> <span class="r">end</span> <span class="no">23</span> <span class="no">24</span> <span class="r">class</span> <span class="cl">PrivatePictureTest</span> &lt; <span class="co">Test</span>::<span class="co">Unit</span>::<span class="co">TestCase</span> <span class="no"><strong>25</strong></span> include <span class="co">PicturePathHelper</span> <span class="no">26</span> <span class="no">27</span> <span class="r">def</span> <span class="fu">setup</span> <span class="no">28</span> pict = uploaded_file(<span class="s"><span class="dl">'</span><span class="k">large.jpg</span><span class="dl">'</span></span>, <span class="s"><span class="dl">'</span><span class="k">image/jpeg</span><span class="dl">'</span></span>, <span class="s"><span class="dl">'</span><span class="k">somepicture.jpg</span><span class="dl">'</span></span>) <span class="no">29</span> <span class="iv">picture = Picture.build(pict)
30 picture</span>.public = <span class="pc">false</span> <span class="no">31</span> assert_model_saved <span class="iv">picture
32 end
33
34 def test_picture_not_copied_to_disk_when_private
35 assert !File.exist?(picture</span>.cache_filename), <span class="no">36</span> <span class="s"><span class="dl">'</span><span class="k">image should not be on disk when picture is private</span><span class="dl">'</span></span> <span class="no">37</span> <span class="r">end</span> <span class="no">38</span> <span class="no">39</span> <span class="r">def</span> <span class="fu">test_thumbnail_not_copied_to_disk_when_private</span> <span class="no"><strong>40</strong></span> assert !<span class="co">File</span>.exist?(<span class="iv">picture.cache_thumbnail),
41 thumbnail should not be on disk when picture is private
42 end
43 end

I also had another flash: is TestCase the right name ? Shouldn’t it be called TestFixture instead ? Because that is what instances of this class represent – a test fixture.

This is very much akin to Spec::Context, from RSpec.

And you see the tests ? They all respect the one assertion per test. I was quite happy with the results.

Now, I need to refactor those other instances in my test suite…

Named Test Fixtures

2005-10-18

In one of my projects, I have tons of units and functional tests. To speed things up, I set my configuration like this:

test/test_helper.rb

1 class Test::Unit::TestCase
2 self.use_transactional_fixtures = true
3 self.use_instantiated_fixtures = :no_instances
4 end

In my tests, instead of accessing @bob, I now have to do:

test/unit/party_test.rb

1 def test_party_is_destoryed
2 party = Party.find(@parties[bob][id])
3 party.destroy
4 assert party.destroyed?, "Party wasn’t marked destroyed"
5 end

Works, but isn’t this very verbose ? So, I created myself a little helper::

test/test_helper.rb

1 def party(fixture_name)
2 Party.find(@parties[fixture_name.to_s][id])
3 end

All great and dandy. I wanted to share with the community, so I started digging into fixtures.rb to implement it. Much to my surprise, this already exists:

vendor/rails/activerecord/lib/active_record/fixtures.rb

1 class Test::Unit::TestCase #:nodoc:
2 def self.fixtures(*table_names)
3 table_names = table_names.flatten
4 self.fixture_table_names |= table_names
5 require_fixture_classes(table_names)
6 setup_fixture_accessors(table_names)
7 end
8 end

So, I guess I’m going to send a documentation patch instead of code !

UPDATE 1 (Oct 18, 2005): Well, would you look at that, this is all documented in the Class: Fixtures (See the last paragraph under Using Fixtures)

UPDATE 2 (Oct 25, 2005): Rails 0.14.1 is pre-configured like that. In fact, Mike Clark wrote Faster Testing with Rails 1.0 to explain the new behavior.

Search

Your Host

A picture of me

I am François Beausoleil, a Ruby on Rails and Scala developer. During the day, I work on Seevibes, a platform to measure social interactions related to TV shows. At night, I am interested many things. Read my biography.

Top Tags

Books I read and recommend

Links

Projects I work on

Projects I worked on