Using Model factories with RSpec
Posted by Craig Ambrose on February 23, 2008 at 10:12 PM
I had a lot of questions and concerns about RSpec when I first made the switch a few months ago. I’d seen a few talks on BDD in general, and watched the peepcode screencasts on rspec, and I could see that BDD tended to encourage a fairly good style of testing. I managed to find answers for most of my questions, so here’s a little list of the challenges and resolutions that I faced when I made the switch.
When I first started on rails, like a good little agile developer, I mocked out everything for my unit tests and ensured that I never actually hit the database and thus never required fixtures. I used the mocha library predominantly, but after a month or so of this I had gotten totally tangled up.
The problem was that active record objects are a bit like an iceberg where only a part of them is visible in the code, and the rest is lurking below the murky waters of your database. My tests were missing heaps of bugs caused by queries generating invalid SQL, but reliance upon properties which may or may not exist depending on the database schema, and were also far too complicated as they required lines and lines of setup code to handle all the wacky ways in which a model could interact with a database.
My solution eventually was to give in, and test things the way DHH does. Not particularly elegant, certainly not “correct” in terms of what I’d been taught regarding how to test, but it did work. The upside of these tests which often integrated a number of layers together was that I got great coverage. The downside was that I’d often get a raft of test failures if I broke a single line of code, and some things were just a bit too much effort to test and tended to motivate me not to bother. Also, the dependence on fixtures meant that as the test situations got more complex, I’d have to add more fixtures, and in doing so break other tests.
When moving to RSpec, I threw all this away, and started again on mocking everything out. RSpec helped out with a few nice extra methods, like mock_model, which can sensibly provide a mock ActiveRecord object that has the bare essentials already (like the id and to_param methods). This time I did better, but I still got a little tangled up, and I found it really hard to test methods where I was querying for record. With a mock based approach, all I could test was that I’d sent the query that I expected, not that it was actually valid SQL or that it would fetch any sensible results.
The resolution, as it turns out, lies somewhere in between.
I use mocking pretty heavily still, particularly when I’m creating or updating records. But, I also often want to deal with real data, particularly when it’s being fetched rather than created. Fixtures were a bad idea, because they created a great big data set that had to be valid for all tests. There are some plugins to provide different sets of fixtures for different scenarios, but I think that having the data used for a test in a different file is also downright confusing. Setting up for each test needs to be easy.
The solution for me was the use of a model factory, which is a pattern in some use amongst my colleagues at Cogent. I’d left the worlds of C# and C++ with a bit of a dislike of factories, being a pattern that, like dependency injection, seemed to add a great deal of complexity to a problem that is much more easily solved in a dynamic language. However, a simple model factory for testing is a different kettle of fish.
Lets say I’m testing that I can’t create a user if their email address already exists. If I was using fixtures, I’d create a user with the email address I was going to try out, but that would affect all other tests. If I created a user directly in the test then I’d need to update my test each time I changed the information needed to create a valid user. Instead, a model factory makes it look like this:
describe User, "when being created" do
it "should require email address to be unique" do
model_factory.user(:email => "craig@craigambrose.com")
user = User.create(:email => "craig@craigambrose.com")
user.should_not be_valid
user.errors.on(:email).should == "must be unique"
end
end
The user method on model_factory creates and returns a valid user. It can be called as many times as I want, and it will always return a valid user. If User contains fields that must be unique, then the data I use to populate it contains integers which increment each time. The factory methods also take arrays of options which can be used instead of these default values, which I use whenever I want to set an attribute that I care about in the test. This way, if I change what is required to create a user, all I need to change is the model factory, not every single test.
I don’t use the model factory all the time. I still make heavy use of mock_model. For each case, I try and determine which method will yield the simplest test that actually forces me to write the code that is needed, rather than just appearing to give coverage over the lines of code. Sometimes I use some mocking and some concrete objects in the same test.
I haven’t provided the code for the model_factory itself. It’s very straightforward and I’ll leave it as an exercise for the reader. Drop me a line if you have a particularly clever implementation that you’d like to share or are interested in seeing some of my code.

Comments
There is 1 comment on this post. Post yours →
Woah—cool. This sounds like it’s almost identical to the pattern we found and implemented in object_daddy. I see that Dan Manges and Zak Tamsen are giving a talk including some model factory content at Railsconf this year.
Thanks for writing this up.
Rick
Post a comment
Required fields in bold.