DISQUS

Dan Manges's Blog: Dan Manges - Fixin' Fixtures with Factory

  • Daniel Ha · 2 years ago
    Let us know if you run into any problems. Thanks for being an alpha tester!
  • richard · 2 years ago
    This is great though as a newbie, would you include the Factory module file by putting a require in test_helper.rb?

    Thanks
  • Dan Manges · 2 years ago
    Hi Richard - Yes, I would define the factory in test/factory.rb and then require it from test_helper.rb.
  • Erik Ostrom · 2 years ago
    I've been taking a similar approach with rspec, although I came to it from a slightly different direction.

    For my controller and view specs, I have an elaborate set of helper methods that construct fake model objects, with overridable default parameters (as in your fixture factory) and fake associations. (My mock_model method takes a hash of associations, stubs a couple of methods on the current mock object, and also stubs a reverse method on the associated mocks.)

    Rspec advises hitting the database in model specs, so I can't use my mock helpers, but I got really used to the convenience of them. So I've started building a parallel set of model helpers that speed up the tedious "make the object valid" part. My favorite is create_spec_user, which generates a valid password, password confirmation (demanded by restful_authentication's User class), and unique email, based on the spec-provided login or a default.

    I've seen some people take the philosophical stance that test objects (and mocks) should be static and not smart, to ensure you're always testing the same thing. But so far I've found this approach helps me focus on the things I'm actually trying to test, instead of the drudgery of fixture maintenance.
  • Dylan · 2 years ago
    Excellent pattern. Very easy to read, and straight-forward.

    Just wondering... have you tried this on edge ?
    The relationship building you do (ie: :paperbody => create_paperboy), doesn't seem to save the association.

    Also, can't wait for you to post your modifications to this :)
  • Dan Manges · 2 years ago
    I'm using it on Rails 1.2.3 and have not tried on edge.
  • Law · 2 years ago
    I second dylan's issue. on 1.2.3 i get a blank association error when adding a paperboy to a newspaper. i noticed on the test_paperboy_delivers_to function you create a paperboy and a customer and manually add them to a create_newspaper function. why is that? because on the definition of create_newspaper you seemingly create the customer and paperboy by default.

    i also got the file uploads working by creating a private function in the Factory module.

    def file_upload(path, mime_type = nil)
    ActionController::TestUploadedFile.new(
    Test::Unit::TestCase.respond_to?(:fixture_path) ? \ Test::Unit::TestCase.fixture_path + path : path,
    mime_type)
    end

    this is actually just the code from within the fixture_file_upload method but i couldn't get that to work from within the module. so it works just like fixture_file_upload

    ie. file_upload('/docs/test1.ppt', 'application/vnd.ms-powerpoint')
  • Law · 2 years ago
    nice solution.

    i was wondering how one would go about including fixture_file_uploads in the creates.
  • Justin · 2 years ago
    Very cool. I used to do something similar with create_* helpers directly in test_helper.rb. This of course cluttered things up a lot. Pulling them out into a Factory seems like a very elegant solution. One thing I might add is pulling the default attributes out into methods themselves, for use when testing creates/updates in the controller.
  • Scott Taylor · 2 years ago
    What a great idea! I've packaged your Factory into a rails plugin named "FixtureReplacement", which DRY'es up some of the duplication (and takes care of another issue). The plugin is on Rubyforge:

    http://replacefixtures.rubyforge.org/

    There is also a screencast here: http://railsnewbie.com/files/fixture_replacemen... (the screencast features your site - hope you don't mind the free promotion :) I would be interested in your feedback on the plugin.

    Thanks again.
  • Henrik N · 2 years ago
    If one's models use attr_accessible/attr_protected and you want to be able to set protected attributes in factories, this is one way: bttp://henrik.nyh.se/2007/10/bypassing-attr_accessible-and-attr_protected-for-test-factories
  • Scott Taylor · 2 years ago
    Dan,

    Don't know if you gotten the chance to check out my plugin yet, but just wanted to let you know that it now works with attr_protected.

    Hope all is well,

    Scott
  • Ben · 2 years ago
    The only problem I have run into is that the objects are created when setting up the default_attributes hash - even if they are not used. This is no problem for strings, but in your example above create_customer always calls create_paperboy.

    Obviously, this has some impact on the speed of the test (not sure if it's really noticeable or not), but the bigger problem for my project is that it messes up tests that check how many records are created or destroyed. For instance, I have test that checks to make sure the number of Users is one greater after performing a certain action. This method will break that.

    Perhaps that's not a good way to test record creation and deletion, but it is pretty common - I believe the tests that are output when you use scaffold have a similar style. I wonder if there is a simple way to lazily evaluate the code in default_attributes? Perhaps wrapping each expression in a Proc or something?
  • Scott Taylr · 2 years ago
    Yes Ben - this was my idea exactly (checkout FixtureReplacement, which does this - see the class called DelayedEvaluationProc). So with FixtureReplacement, you could do something like this:

    create_customer(:paperboy => nil)

    And this way no paperboy would be created. Or, you could just remove it from your defaults. Your call, ultimately.
  • Ben · 2 years ago
    Scott,

    Sounds cool. I'll check out FixtureReplacement. Thanks for creating it!

    Ben
  • Nic · 2 years ago
    HI there - this is the exact process I'm trying to implement (I'm new to rails - but not tdd). I'm having problems with the relationships though. I'm using unit_record but when my test executes the code which updates the relationship - in your example above that would be :paperboy => create_paperboy it throws an exception as the Create! attempts to call Save! on the activre record object. Any ideas?
  • Dan Manges · 1 year ago
    You want to use unit_record in unit tests and factory in functional tests. Take a look at the 'restructuring the test directory' section of the unit_record readme: http://unit-test-ar.rubyforge.org/files/README....

    For any given model (let's say person), you'll have a test/unit/models/person_test.rb that uses unit_record and a test/functional/models/person_test.b that uses a factory and other database logic.
  • golubeff · 1 year ago
    Seems, like I've found a bug. valid_#{factory_name.to_s}_attributes redefines @default_attributes. Should it?

    --- trunk/website/vendor/plugins/model_factory/lib/factory_builder.rb (revision 50)
    +++ trunk/website/vendor/plugins/model_factory/lib/factory_builder.rb (revision 83)
    @@ -46,14 +46,15 @@
    Factory.send :define_method, :"valid_#{factory_name.to_s}_attributes" do
    instance_variable_set("@default_attributes", @default_attributes)
    + result = default_attributes.dup

    # if they stored a proc, make it nil
    - default_attributes.each do |key, value|
    - default_attributes[key] = value.call if value.is_a?(Proc)
    + result.each do |key, value|
    + result[key] = value.call if value.is_a?(Proc)
    end
    - default_attributes.select {|key, value| value == :rand }.each do |it|
    + result.select {|key, value| value == :rand }.each do |it|
    types = factory.to_s.classify.constantize.const_get(it.first.to_s.pluralize.upcase)
    - default_attributes[it.first] = types[rand(types.length)]
    + result[it.first] = types[rand(types.length)]
    end
    - return default_attributes
    + return result
    end
  • Rodrigo · 1 year ago
    You should check association parameters:

    :paperboy => attributes[:paperboy] || create_paperboy

    You will end with two paperboys in the database otherwise.
  • Rick Bradley · 1 year ago
    Excellent. If I'd known the name of the pattern was basically 'model factory' it would've saved me some work :-) We implemented a similar thing (written bdd spec-first, and with what we think is a cleaner approach in a couple of places) called object_daddy for very similar reasons. There's a long-ass writeup here (http://b.logi.cx/2007/11/26/object-daddy), we're still hosting our own git repo for the thing but it and the rest of our open code is moving shortly to github. We'll also be speaking at railsconf, so maybe I'll get to catch your talk there.

    Thanks!
    Rick
  • Zach Dennis · 1 year ago
    Another good writeup can be found at:

    http://spin.atomicobject.com/2008/04/30/model-g...

    It's what I've been using for the past year or so.
  • David Lowenfels · 1 year ago
    I've taken Nathan Herald's factories-and-workers plugin, dusted it off and given it some love over at http://github.com/dfl/factories-and-workers/