Factories Make Testing Easier
Last month I posted the following gist, asking people which they preferred. The choice comes down to using mocks or hitting the database. Almost everyone who answered via twitter preferred the 2nd approach. If you ever read this blog, you'll know that I agree.
Rob Conery suggested that I use Factory_Girl (FG) to make example #2 even better. Rob and Dave had talked to me about FG in the past, but I just wasn't really grasping how useful it would really be.
Fast forward to my latest project, and I figured I'd give it a try. I'm an idiot for having waited so long.
The general idea behind FG (or any similar framework) is that you define a factory for each of your object, setting reasonable defaults as needed. This has three significant implications when writing tests. First, your tests require less setup, and are thus cleaner and easier to maintain. Second, factories tend to make your tests more explicit by really focusing on data which is important (versus data which is simply necessary). Finally, when your model changes and a new required field is added, you don't have to go update a bunch of seemingly unrelated tests and add irrelevant data.
As a simple example, we might create a factory for our game
object like so:
Factory.define :game do |g|
g.secret "it's over 9000"
g.name "power level?"
end
We can now create a new instance within our tests by using game = Factory.build(:game)
. Alternatively, we can create and persist the same object by using create
instead of build
. We can also pass either method an additional parameter which are the values to use instead of the defaults: Factory.create(:game, {:secret => 'what?!'})
. Factories support more advanced logic. For example, sequences let us ensure a unique value for each built/created instance:
Factory.sequence :name do |n|
"duncan-#{n}"
end
Factory.sequence :email do |n|
"duncan#{n}@dune.gov"
end
Factory.define :developer do |d|
d.name 'duncan'
d.email {Factory.next(:email)}
d.status DeveloperStatus::Enabled
end
I mentioned that a benefit of this approach is that it makes our tests more explicit. Let me give you a real example of what I mean. We have a method which lets us activate a developer. This essentially switches his or her status from Pending
to Enabled
and saves the object. Here's the test:
it "enables the developer" do
developer = Factory.create(:developer, {:status => DeveloperStatus::Pending})
developer.activate!
developer.status.should == DeveloperStatus::Enabled
Developer.count({:_id => developer.id, :status => DeveloperStatus::Enabled}).should == 1
end
Since I'm not having to create a developer with a bunch of extra fields, the test is very clear about what data it cares/behaves on (the status
). In fact, even if the default status was Pending
, I still think it's worth explicitly setting it in the test - both so that a change to the factory doesn't break this test and also to make the test well documented.
The whole thing is full of win. It makes tests easier to write, maintain, read and makes them less brittle to unrelated changes. ObjectMother SmobjectMother.
C# developers will likely want to look at either Plant or nbuilder. While these provide decent build capabilities, the persistence story isn't quite as clean/simple as what you'll get from Ruby/Rails. In fact, for this specific project we are using our own thin mapper and FG just dynamically invoked our save
method - 'cuz that just makes sense. NBuilder can invoke a callback, but that'll likely require some extra wiring for more complex and typical (DI-heavy, over-engineered (ya, I said it)) .NET scenarios.