Giter Site home page Giter Site logo

smtlaissezfaire / fixturereplacement Goto Github PK

View Code? Open in Web Editor NEW
17.0 4.0 4.0 529 KB

FixtureReplacement rails plugin

License: GNU General Public License v3.0

Ruby 100.00%
factorybot fixtures testing fixturereplacment data-generation factory test testing-tools ruby factory-bot

fixturereplacement's Introduction

FixtureReplacement

What is FixtureReplacement?

FixtureReplacement is a Rails gem that provides a simple way to quickly populate your test database with model objects without having to manage multiple, brittle fixture files. You can easily set up complex object graphs (with models which reference other models) and add new objects on the fly.

(If you’ve ever used FactoryGirl / FactoryBot, you’re probably already quite familiar with fixture_replacement, as it proceeded FactoryGirl. Also, FR is much more opinionated, and much less PC!)

Not only can FixtureReplacement make your test data easier to maintain, it can also help to make your tests and specs much more readable and intention-revealing by allowing you to omit extraneous details and focus only on the attributes that are important for a particular behaviour. It works well with both RSpec and Test::Unit.

What’s new since 2.0:

  • default_* is gone in favor of new_*.

  • Cyclic dependencies are no longer an issue. The “overrides hash” (the hash passed to new_* or create_*) can now be processed.

See CHANGELOG.rdoc + test suite for further changes.

Installation

Add it to your Gemfile:

group :development, :test do
  gem "fixture_replacement", "~> 4.0"
end

Using it with RSpec

Add the following to your spec/rails_helper.rb file, in the configuration section:

RSpec.configure do |config|
  config.include FixtureReplacement
end

Using it with Test::Unit

Add the following to your test/test_helper.rb file:

class Test::Unit::TestCase
  include FixtureReplacement
end

How to use FixtureReplacement

Defining default attributes

At the heart of FixtureReplacement is the db/example_data.rb file where you define the default attributes for each of your test models. This example shows the default attributes for a user:

module FixtureReplacement

  attributes_for :user do |u|
    password = random_string

    u.value                  = "a value",
    u.other                  = "other value",
    u.another                = random_string,     # random string 10 characters long
    u.one_more               = random_string(15), # 15 characters long
    u.password               = password,
    u.password_confirmation  = password,
    u.associated_object      = new_bar            # expects attributes_for :bar to be setup
  end

end

Note that:

  • A ‘random_string’ method is provided for attributes whose exact value isn’t important; this means you can create multiple, unique model instances

  • you can perform arbitrary set-up and execute any Ruby code prior to returning the hash (as shown here where a password is generated and then used for both the :password and :password_confirmation attributes)

  • a new_modelname method is automatically provided that allows you to set up dependent model objects (in this case an instance of the Bar model)

Available methods

Based on the above definition FixtureReplacement makes the following methods available:

  • random_string: generates a random string as shown above

  • new_user: equivalent to User.new with the attributes for the user.

  • create_user: equivalent to User.create! with the user’s attributes.

  • valid_user_attributes: returns a hash of the user’s attributes including associations, specified in db/example_data.rb.

Overriding attributes

Overrides of specific attributes can be performed as follows:

new_user(:thing => "overridden")
create_user(:thing => "overridden")

Overrides can also be used with associations:

scott = create_user(:username => "scott")
post = create_post(:user => scott)

Validate your fixtures (thanks Fixjour)

Validate your fixture definitions after including it in the spec helper or test helper:

spec_helper.rb:

Spec::Runner.configuration do |config|
  config.include FixtureReplacement
end

FixtureReplacement.validate!

test_helper.rb

class Test::Unit::TestCase
  include FixtureReplacement
end

FixtureReplacement.validate!

Using FixtureReplacement within script/console

$ ./script/console
Loading development environment
>> include FR
=> Object
>> create_user
=> #<User id: 1, crypted_password: "521faec1c095..." ...>

Philosophy & Disadvantages

See philosophy_and_bugs.rdoc

Contributors, Contributions, & BUGS

See contributions.rdoc

License

This software is dual licensed under the MIT and the GPLv3 Licenses (it’s your pick).

Copyright 2007-2022 Scott Taylor / smtlaissezfaire ([email protected])

fixturereplacement's People

Contributors

dependabot[bot] avatar nodanaonlyzuul avatar smtlaissezfaire avatar wincent avatar wolfmanjm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

fixturereplacement's Issues

Possible design wrinkle in the way models are instantiated

Just jumped from version 1.0 to 3.0 and a spec which used to work stopped working:

it 'should require it to be present on new records' do
    new_user(:passphrase => nil, :passphrase_confirmation => nil).should fail_validation_for(:passphrase)
end

On digging deeper using FixtureReplacement inside "script/console", I saw that the ":passphrase" attribute wasn't getting set to nil. Workaround was to rewrite that one spec using an old-fashioned manual "User.new".

Trying to find out why that one spec failed when all my other specs involving FixtureReplacement kept on working, I look into the FixtureReplacement source code I see this method inside attribute_builder.rb:

def instantiate(hash_to_merge = {}, instance = active_record_class.new)
  returning instance do
    instantiate_parent_fixture(instance)
    call_attribute_body(instance, hash_to_merge)
    add_given_keys(instance, hash_to_merge)
  end
end

My understanding, then, of the code, is that it creates instances (via "User.new" in this case), calls the code block that was elsewhere set-up using "attribute_for", and as a last step applies the overrides.

So in my case, the first thing that gets called after the "User.new" is defined here:

module FixtureReplacement
  PASSPHRASE = 'supersecret'
  attributes_for :user do |u|
    u.display_name            = random_string
    u.passphrase              = PASSPHRASE
    u.passphrase_confirmation = PASSPHRASE
    u.verified                = true
  end
end

And then the overrides get applied using:

instance.send("#{key}=", value)

Which in the case of the cited spec effectively boils down to:

user.passphrase = nil
user.passphrase_confirmation = nil

So, the first line isn't working but the second one is. The reason why can be found by looking in my User model code, which has this custom attribute setter:

# Stores a new passphrase_salt and combines it with passphrase to generate and store a new passphrase_hash.
# Does nothing if passphrase is blank.
def passphrase= passphrase
  return if passphrase.blank?
  @passphrase = passphrase
  salt = User.random_salt
  self.passphrase_salt, self.passphrase_hash = salt, User.digest(passphrase, salt)
end

I won't go into the gory details, but the basic reason for that "return if passphrase.blank?", combined with other code in my validations and an "after_save" hook which I haven't pasted in here, is to allow me to update user passphrases only for new records, or when the user supplies a matching "passphrase_confirmation" and also an "old_passphrase" value, but not when a user is merely updating other attributes in their profile.

So, effectively the sequence of calls on those attributes ends up being:

user.passphrase = 'supersecret'
user.passphrase_confirmation = 'supersecret'
user.passphrase = nil
user.passphrase_confirmation = nil

And the "passphrase = nil" one doesn't have any effect.

This could be a design wrinkle in my User model, but I can't really see how else to handle the different pathways (new record -> new passphrase and confirmation, old record -> other updates but no passphrase updates, old record -> passphrase change requiring confirmation and knowledge of old passphrase).

Ideally, FixtureReplacement wouldn't first set the attributes and then override them. Rather it would set the attributes only once, including the overridden values.

But looking at how the code is implemented (just by calling that block), I can't really think of a way how that could be done. (And I am not suggesting that this block technique be abandoned as it is actually very simple and elegant and makes the codebase very readable.)

So I think perhaps what I am saying is that, unless a way can be found to apply overrides once and only once, the docs probably need to explicitly state how objects are created; ie. something like:

"Instances are created in three steps: first, the #new method is called on the object's class, then the initial set of valid attributes is applied by invoking the code block defined using #attributes_for, and finally any overrides passed in using the optional hash arguments are applied."

Don't know if that would be enough to help people avoid this potential pitfall.

Evidently, one other alternative I should mention is that I realize that I could take advantage of the optional hash parameter to the "attributes_for" method to do something like:

PASSPHRASE = 'supersecret'
attributes_for :user do |u, hash|
  u.display_name            = random_string
  u.passphrase              = hash.has_key?(:passphrase) ? hash[:passphrase] : PASSPHRASE
  u.passphrase_confirmation = PASSPHRASE
  u.verified                = true
end

But it's a bit cumbersome and my other workaround (just using User.new instead of FixtureReplacement in that one spec) is more appealing.

Deprecation warning (RAILS_ROOT) under Rails 3

Just started experimenting with Rails 3 and am seeing deprecation warnings on starting up the server or the console in the Rails development/test environments:

DEPRECATION WARNING: RAILS_ROOT is deprecated! Use Rails.root instead. (called from load_example_data at src/vendor/gems/fixture_replacement-3.0.1/lib/fixture_replacement/class_methods.rb:20)

Haven't actually got the app up and running yet under Rails 3, so can't confirm whether FixtureReplacement actually works or not yet.

If I can get it working I'll see if I can whip up a patch to silence the warning.

Cheers,
Wincent

builder validations

Validate builders by calling valid? on each of them, and failing if any are invalid

gem

Should bundle it as a gem as well as a plugin. Add gems for previous versions.

Validate keys given to attributes_for

The following should raise an InvalidOption error:

attributes_for :pencil_order, :class_name => Pencils::Order

:class_name isn't a valid key - only :class is.

Fix typo in README

Just did this in my fork:

wincent/fixturereplacement@f0d6ada3424fb0d02dfcb9a474e751e30fe37662

(Ugh, don't like the GitHub way of doing things with forks. Would much rather just send a patch, but their issue tracker doesn't have accept attachments. Here it is as a "gist": http://gist.github.com/239777 )

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.