Skip to content

Instantly share code, notes, and snippets.

@nakajima
Created February 8, 2009 13:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nakajima/60389 to your computer and use it in GitHub Desktop.
Save nakajima/60389 to your computer and use it in GitHub Desktop.

Why I Wrote Fixjour

On the RSpec mailing list, there have been some posts recently about testing, and more specifically, object creation method frameworks like fixturereplacement and my own Fixjour. Scott Taylor raised some really good points about designing these sorts of tools, as did others. I was going to post a response on there, but then it got long, and I decided to start a blog.

Here's where Fixjour fits in the mix.

The main reason I created Fixjour was to have an object creation method generator that supported the API I like (which is close to that of FR) and absolutely nothing else. On top of that, I wanted it to actively prevent things that had caused me pain in the past, such as builders returning invalid objects when domain logic changes, redundant builder methods being defined (causing ambiguity), and basically just using the tool wrong.

Fixjour.verify!

In Fixjour, I added a Fixjour.verify! method, which runs though each of your builders and checks the following:

  • The new object is valid?
  • The new object is a new_record?
  • The new object is of the appropriate class (handled automatically these days)
  • The new object can be saved (meaning there's no DB issue to contend with)

It also does a check to see if an object is still valid after one has already been saved, which offers a bit of protection against issues that arise due to things like validates_uniqueness_of.

Doing this check up front means that there's never a divide between your object creation methods and reality. You're failing faster, and people say that's a good thing.

The overrides hash

I refer to the hash of options you pass to new_foo or create_foo as the "overrides" hash. It's the reason this pattern is so good. The problem is that it's often abused. Oftentimes you'll see code like this:

define_builder(Person) do |klass, overrides|
  is_child = overrides.delete(:child)
  overrides[:dob] = is_child ? 2.years.ago : 42.years.ago

  klass.new(:name => "Pat")
end

I find that code much harder to read. The thing is though, it's oftentimes really convenient to be able to specify things like this. Your tests can be much better for it. I just wanted a way to reduce the pain of doing so.

To start, I made the #delete method on the overrides private and provided a #process method which can be used to process option explicitly. This removes the need for if overrides.delete(:name) and friends. It also documents quite clearly the places where you're messing with things.

To me, this reads better:

define_builder(Person) do |klass, overrides|
  overrides.process(:child) do |is_child|
    overrides[:dob] = is_child ? 2.years.ago : 42.years.ago
  end

  klass.new(:name => "Pat")
end

The Philosophical Issues

Scott brought up a few points where I just disagree.

Couldn't I just say create_admin instead of create_user(:admin => true)

Since I use these methods primarily in very isolated model unit tests, it doesn't serve me well to wrap the :admin => true in a separate builder. Being able to see that I'm actually getting an instance of User is more useful I've found.

If I did feel the need for a create_admin helper, it might be a sign that I need an Admin model. If that's not the case, I'd prefer to write the helper manually.

do they support attr_protected attributes

Fixjour doesn't do this, and while it may seem like a bug, I like the builders to be instantiating a new object just as they would in the wild. That means attr_protected fields are documented in the test cases. I could see supporting a more convenient assignment, but it's not a big priority for me.

And so...

The philosophical differences above set Fixjour and FR apart enough to warrant separate projects. Please note that I'm just expressing my own opinions here, and if you disagree, it means nothing more than the fact that Fixjour probably isn't for you. Really, it all comes down to what makes you feel more effective, and once you do, nothing else matters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment