When I first became test-infected, I did not give much thought to how I set up the objects used in the examples. Rails shipped with yaml fixtures, so I used those but would on occasion just make new instances when the setup was minimal. They looked something like this:

When I first became test-infected, I did not give much thought to how I set up the objects used in the examples. Rails shipped with yaml fixtures, so I used those but would on occasion just make new instances when the setup was minimal. They looked something like this:

    describe User do
  context "admin" do
    subject { User.new :admin => true }

    it { should be_omniscient }
    it { should be_omnipotent }
  end

  context "regular user" do
    subject { User.new :admin => false }

    it { should_not be_omniscient }
    it { should_not be_omnipotent }
  end
end
  

Okay they didn’t look exactly like that, I’m taking advantage of RSpec’s sweet implicit subject feature, but you can see that I’ve just created a new instance and passed it some attributes.

There are some problems with newing up objects like that. Often you will need a valid object, such as in an integration test that expects the record to be in the db. When you do, the test looks more like

    describe User do
  context "admin" do
    subject { User.new :admin => true, :name => "Joe the Administrator", :employee_id => "abc123" }
    it { should be_omniscient }
    it { should be_omnipotent }
  end

  context "regular user" do
    subject { User.new :admin => false, :name => "Joe Schmo", :employee_id => "foobar" }

    it { should_not be_omniscient }
    it { should_not be_omnipotent }
  end
end
  

The first problem is the newly required attributes make the example less clear. What is it that makes a User an admin? Is it the admin field, the name, or the employee id? You can be helpful and position the important attributes first, and use good naming conventions, but it would be nice to get rid of the noise.

A much more painful problem that you will experience soon is what happens when you add a new validation to the model. Specs start breaking all over the place because they’re expecting a valid object. Now you get to go back and add the new field to every single example. No fun.

It can be a good idea to separate object graph construction from use, but it’s especially important in tests. As we just saw, failure to do so results in tests that are too tightly coupled to the implementation. You need to build test data somehow. There are many different patterns, and lots of different tools for Ruby. I’m going to show you my favorite, Fixjour.

Easy fixture setup

When I say fixture, I mean an object you set up for use within a test, not the yaml files that Rails automatically loads into the db. I will refer to those as yaml fixtures or database fixtures - Rails hijacked the term fixture and I’m taking it back :)

Fixjour lets you easily define a builder for your models:

    Fixjour do
  define_builder(User) { :name => "Pat", :admin => false, :empid => "abc123" }
end
  

This gives you a few methods to build objects for your tests. Here are the examples from before, using Fixjour to do the setup:

    describe User do
  context "admin" do
    subject { new_user :admin => true }

    it { should be_omniscient }
    it { should be_omnipotent }
  end

  context "regular user" do
    subject { new_user :admin => false }

    it { should_not be_omniscient }
    it { should_not be_omnipotent }
  end
end
  

The new_user method returns a non-admin User named Pat, with an employee id of abc123. You can override options by passing them in, which is what I did with the admin field. The test makes it obvious that the admin field changes a User object’s behavior, and now when adding validations, you will only have to modify the builder definition.

Tips

That’s Fixjour at its simplest. Now I want to share some of my most used features, along with some general tips on setting up test data.

new_* / create_* / valid_attributes

In addition to the new_user method, defining a builder for User also gives us a create_user and valid_user_attributes method. new_user returns an unsaved object, whereas create_user will save it to the database before passing it back.

valid_user_attributes returns a hash that you could pass to User.new and get a valid object. It’s useful when you want an attributes hash rather than an object, such as testing the update action in a controller.

Hash or block construction

When defining a builder, you can return a hash which Fixjour will then pass to the object’s initialize, or you can build an object yourself. I already showed returning a hash. Here’s an example of building the object. Notice that Fixjour yields the class that you’re defining the builder for.

    define_builder(User) do |klass|
  klass.new {:name => "Pat", :admin => false, :empid => "abc123"}
end
  

I almost always use the hash form. Looks nicer to me. I only use the klass form when I have to do set up than just a simple hash, such as when I’m adding objects to a has_many association.

Counters

You might have noticed that I’m hard-coding values in the builder. That won’t work in examples requiring multiple records, and you have a validates_uniqueness_of somewhere. You could use a Guid, but Fixjour provides counters that you can use to generate unique data.

    define_builder(User) { :name => "Pat", :admin => false, :empid => "abc#{counter(:user)}" }
  

counter is just a simple incrementing counter, so if you call new_user a few times they’ll all have different employee IDs. You can pass counter a name to scope the counts, so you could have counter(:user) increment separately from counter(:blog_post)

Associations

You can automatically set up associations by defining builders for each model and then calling one from the other.

    define_builder(BlogPost)
  

Be explicit, be-e explicit

Look at the first Fixjour example I gave, where I define a builder that defaults the admin flag to false. When I want to test the admin behavior, I pass it in as false even though it’s already set to false by default. This makes the test clearer by communicating the important components, and reduces coupling between tests and the test builder. If I don’t pass it in, and down the road I change the default to true, the test will fail. This is part of the problem with Rails’s built-in database fixtures. Each test that uses a particular fixture relies on the data within that fixture, and when you change that it ripples out to other tests, forcing you to tweak those tests or create a whole new set of fixtures.

The important thing to remember is that you’re just asking for a valid object - what’s inside of it doesn’t matter unless you explicitly set it. Keep that in mind whether you’re using Fixjour or any other test data framework, and you should end up with more maintainable tests.

Where to go from here

There’s some good documentation at the Fixjour github repo so check it out.

blog comments powered by Disqus