I'm writing a gem that can be used both with and without rails. In a few places I use code like
path = Rails.root if defined?(::Rails)
and I want to test this logic with rspec. I have tried stubbing it like
stub(:"::Rails").should_receive(:root).and_return("/rails")
but this does not make defined?(::Rails) evaluate to true.
Even if defined?(::Rails) is evaluated to true, you still need a Rails object to inject the method stub. There might be several ways to do this, following is a example of my preferred approach:
before(:each) do
unless defined?(::Rails)
#mocked_rails_class = true
class ::Rails
end
end
end
it do
::Rails.should_receive(:root).and_return('/rails')
your_method.should == '/rails'
end
after(:each) do
# Clean up the Rails class if it's generated by the test case.
Object.send(:remove_const, :Rails) if #mocked_rails_class
end
I'm not sure if it works on all ruby version, but at least it can work on Ruby 1.9.x.
Related
I want to monkey-patch the rspec-rails generator to generates a _form.html_spec.rb template. I know how do that, but not where I should put this patch!
What I've done:
# rspec_generator_patch.rb
module Rspec
module Generators
class ScaffoldGenerator < Base
def generate_views_specs_with_form *args, &block
generate_views_specs_without_form
copy_view :_form
end
alias_method_chain :generate_views_specs, :form
end
end
end
Where I put this file? Thank you.
EDIT:
Ok, I think problem is almost solved. Instead of monkey-patch, I've inherited the specific generators and edited the method. There's the solution:
# lib/generators/rspec_modded/scaffold/scaffold_generator.rb
require 'generators/rspec/scaffold/scaffold_generator.rb'
module RspecModded
module Generators
class ScaffoldGenerator < Rspec::Generators::ScaffoldGenerator
def generate_view_specs
super
copy_view :_form
end
end
end
end
If I do rails g rspec_modded:scaffold is in list and actually work if called manually (rails g rspec_modded:scaffold test).
# config/application.rb
# ...
config.generators do |g|
g.test_framework :rspec_modded, :fixture => false, fixture_replacement: nil
g.fallbacks[:rspec_modded] = :rspec
end
For what I know, every hook_for :test_framework should call rspec_modded generator and rspec should manage the rest (fallbacks). But it doesn't work: for some reason the unit_test generator kick in! What the matter? I really don't understand...
You can put your monkey patch anywhere, as long as it gets loaded. With rails, you'd usually put it in the /lib folder.
Howcome when I use FactoryGirl to create a record and later update said record the factorygirl instance isn't updated? For example if I have the following factory and rspec test:
factory :foo do
bar false
end
Inside of an rspec test:
foo = FactoryGirl.create(:foo)
Foo.first.update_attribute(:bar, true)
expect(foo.bar).to eq(true) #foo.bar is false and will fail
If I change foo.bar in that last line to Foo.first.bar it passes, why the variance? Is the FactoryGirl instance not directly associated to the record? Performing the test expect(foo).to eq(Foo.first) returns true so are they not the same object?
I think you might have to change
expect(foo.bar).to eq(true)
to
expect(foo.reload.bar).to eq(true)
By the way, you might like to know about be_true:
expect(foo.reload.bar).to be_true
I'm new to RSpec and my controllers're using inherited_resources, I have this mock/stub setup like:
describe MarketsController do
def mock_market(stubs={})
#mock_market ||= mock_model(Market, stubs).as_null_object
end
describe "GET index" do
it "assigns all markets as #markets" do
Market.stub(:all){ [mock_market] }
get :index
assigns(:markets).should eql([mock_market])
end
end
end
And this spec fails because there's nothing in the assigns(:markets). After I added:
class MarketsController
def index
#markets = Market.all
end
end
it'll pass so I guess that's because the inherited_resources doesn't call Market.all to get all of the Market instance and thus bypass the stub for Market.stub(:all). The index method I added above is obviously redundant and shouldn't exist at all, so the question is, without call Market.all explicitly, what should I do in my spec to complete the tests? Thanks in advance!
If I am reading the code correctly, inherited_resources first tries to use Market.scoped if it exists. So do you have a scoped scope?
I have an object that inherits from ActiveRecord, yet it has an attribute that is not persisted in the DB, like:
class Foo < ActiveRecord::Base
attr_accessor :bar
end
I would like to be able to track changes to 'bar', with methods like 'bar_changed?', as provided by ActiveModel Dirty. The problem is that when I try to implement Dirty on this object, as described in the docs, I'm getting an error as both ActiveRecord and ActiveModel have defined define_attribute_methods, but with different number of parameters, so I'm getting an error when trying to invoke define_attribute_methods [:bar].
I have tried aliasing define_attribute_methods before including ActiveModel::Dirty, but with no luck: I get a not defined method error.
Any ideas on how to deal with this? Of course I could write the required methods manually, but I was wondering if it was possible to do using Rails modules, by extending ActiveModel functionality to attributes not handled by ActiveRecord.
I'm using the attribute_will_change! method and things seem to be working fine.
It's a private method defined in active_model/dirty.rb, but ActiveRecord mixes it in all models.
This is what I ended up implementing in my model class:
def bar
#bar ||= init_bar
end
def bar=(value)
attribute_will_change!('bar') if bar != value
#bar = value
end
def bar_changed?
changed.include?('bar')
end
The init_bar method is just used to initialise the attribute. You may or may not need it.
I didn't need to specify any other method (such as define_attribute_methods) or include any modules.
You do have to reimplement some of the methods yourself, but at least the behaviour will be mostly consistent with ActiveModel.
I admit I haven't tested it thoroughly yet, but so far I've encountered no issues.
ActiveRecord has the #attribute method (source) which once invoked from your class will let ActiveModel::Dirty to create methods such as bar_was, bar_changed?, and many others.
Thus you would have to call attribute :bar within any class that extends from ActiveRecord (or ApplicationRecord for most recent versions of Rails) in order to create those helper methods upon bar.
Edit: Note that this approach should not be mixed with attr_accessor :bar
Edit 2: Another note is that unpersisted attributes defined with attribute (eg attribute :bar, :string) will be blown away on save. If you need attrs to hang around after save (as I did), you actually can (carefully) mix with attr_reader, like so:
attr_reader :bar
attribute :bar, :string
def bar=(val)
super
#bar = val
end
I figured out a solution that worked for me...
Save this file as lib/active_record/nonpersisted_attribute_methods.rb: https://gist.github.com/4600209
Then you can do something like this:
require 'active_record/nonpersisted_attribute_methods'
class Foo < ActiveRecord::Base
include ActiveRecord::NonPersistedAttributeMethods
define_nonpersisted_attribute_methods [:bar]
end
foo = Foo.new
foo.bar = 3
foo.bar_changed? # => true
foo.bar_was # => nil
foo.bar_change # => [nil, 3]
foo.changes[:bar] # => [nil, 3]
However, it looks like we get a warning when we do it this way:
DEPRECATION WARNING: You're trying to create an attribute `bar'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer` etc.
So I don't know if this approach will break or be harder in Rails 4...
Write the bar= method yourself and use an instance variable to track changes.
def bar=(value)
#bar_changed = true
#bar = value
end
def bar_changed?
if #bar_changed
#bar_changed = false
return true
else
return false
end
end
How does one do this? Couldn't find any examples online... (using rspec 2.5.0 & rails 3.0.5)
Found it in shoulda-matchers: http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/frames
before(:each) do
#attr = { :bar => "foobar" }
end
it "should reject duplicate bar" do
Foo.create!(#attr)
duplicate_bar = Foo.new(#attr)
duplicate_bar.should_not be_valid
end
Not sure if this exactly what you are looking for, but you could check the error messages after the save or update
#widget.save
#untested, but this should be close
#widget.errors.full_messages.include?("validation message you are looking for").should be true
But honestly, this is probably not something that you need to test in your unit tests (if that is where you are placing them). You are basically duplicating unit tests that Rails has already done for you. It would be more appropriate to check for the error message in the view in a cucumber integration test.