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.
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.
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
Scott brought up a few points where I just disagree.
Couldn't I just say
create_admin
instead ofcreate_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.
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.