module RSpec::Rails::Mocks

Public Class Methods

primary_key() click to toggle source
# File lib/rspec/rails/mocks.rb, line 74
def self.primary_key; :id; end

Public Instance Methods

mock_model(string_or_model_class, stubs = {}) { |m| ... } click to toggle source

Creates a test double representing `string_or_model_class` with common ActiveModel methods stubbed out. Additional methods may be easily stubbed (via add_stubs) if `stubs` is passed. This is most useful for impersonating models that don't exist yet.

NOTE that only ActiveModel's methods, plus new_record?, are stubbed out implicitly. new_record? returns the inverse of persisted?, and is present only for compatibility with extension frameworks that have yet to update themselves to the ActiveModel API (which declares persisted?, not new_record?).

`string_or_model_class` can be any of:

* A String representing a Class that does not exist
* A String representing a Class that extends ActiveModel::Naming
* A Class that extends ActiveModel::Naming
# File lib/rspec/rails/mocks.rb, line 67
      def mock_model(string_or_model_class, stubs = {})
        if String === string_or_model_class
          if Object.const_defined?(string_or_model_class)
            model_class = Object.const_get(string_or_model_class)
          else
            model_class = Object.const_set(string_or_model_class, Class.new do
              extend ActiveModel::Naming
              def self.primary_key; :id; end
            end)
          end
        else
          model_class = string_or_model_class
        end

        unless model_class.kind_of? ActiveModel::Naming
          raise ArgumentError.new <<-EOM
The mock_model method can only accept as its first argument:
  * A String representing a Class that does not exist
  * A String representing a Class that extends ActiveModel::Naming
  * A Class that extends ActiveModel::Naming

It received #{model_class.inspect}
EOM
        end

        stubs = stubs.reverse_merge(:id => next_id)
        stubs = stubs.reverse_merge(:persisted? => !!stubs[:id],
                                    :destroyed? => false,
                                    :marked_for_destruction? => false,
                                    :valid? => true,
                                    :blank? => false)

        double("#{model_class.name}_#{stubs[:id]}", stubs).tap do |m|
          m.singleton_class.class_eval do
            include ActiveModelInstanceMethods
            include ActiveRecordInstanceMethods if defined?(ActiveRecord)
            include ActiveModel::Conversion
            include ActiveModel::Validations
          end
          if defined?(ActiveRecord)
            [:save, :update_attributes, :update].each do |key|
              if stubs[key] == false
                RSpec::Mocks.allow_message(m.errors, :empty?).and_return(false)
              end
            end
          end
          m.__send__(:__mock_proxy).instance_eval("            def @object.is_a?(other)
              #{model_class}.ancestors.include?(other)
            end unless #{stubs.has_key?(:is_a?)}

            def @object.kind_of?(other)
              #{model_class}.ancestors.include?(other)
            end unless #{stubs.has_key?(:kind_of?)}

            def @object.instance_of?(other)
              other == #{model_class}
            end unless #{stubs.has_key?(:instance_of?)}

            def @object.__model_class_has_column?(method_name)
              #{model_class}.respond_to?(:column_names) && #{model_class}.column_names.include?(method_name.to_s)
            end

            def @object.respond_to?(method_name, include_private=false)
              __model_class_has_column?(method_name) ? true : super
            end unless #{stubs.has_key?(:respond_to?)}

            def @object.method_missing(m, *a, &b)
              respond_to?(m) ? null_object? ? self : nil : super
            end

            def @object.class
              #{model_class}
            end unless #{stubs.has_key?(:class)}

            def @object.to_s
              "#{model_class.name}_#{to_param}"
            end unless #{stubs.has_key?(:to_s)}
", __FILE__, __LINE__)
          yield m if block_given?
        end
      end
stub_model(model_class, stubs={}) { |m| ... } click to toggle source

Creates an instance of `Model` with `to_param` stubbed using a generated value that is unique to each object. If `Model` is an `ActiveRecord` model, it is prohibited from accessing the database*.

For each key in `stubs`, if the model has a matching attribute (determined by `respond_to?`) it is simply assigned the submitted values. If the model does not have a matching attribute, the key/value pair is assigned as a stub return value using RSpec's mocking/stubbing framework.

persisted? is overridden to return the result of !id.nil? This means that by default persisted? will return true. If you want the object to behave as a new record, sending it `as_new_record` will set the id to nil. You can also explicitly set :id => nil, in which case persisted? will return false, but using `as_new_record` makes the example a bit more descriptive.

While you can use #stub_model in any example (model, view, controller, helper), it is especially useful in view examples, which are inherently more state-based than interaction-based.

@example

stub_model(Person)
stub_model(Person).as_new_record
stub_model(Person, :to_param => 37)
stub_model(Person) {|person| person.first_name = "David"}
# File lib/rspec/rails/mocks.rb, line 210
def stub_model(model_class, stubs={})
  model_class.new.tap do |m|
    m.extend ActiveModelStubExtensions
    if defined?(ActiveRecord) && model_class < ActiveRecord::Base
      m.extend ActiveRecordStubExtensions
      primary_key = model_class.primary_key.to_sym
      stubs = stubs.reverse_merge(primary_key => next_id)
      stubs = stubs.reverse_merge(:persisted? => !!stubs[primary_key])
    else
      stubs = stubs.reverse_merge(:id => next_id)
      stubs = stubs.reverse_merge(:persisted? => !!stubs[:id])
    end
    stubs = stubs.reverse_merge(:blank? => false)

    stubs.each do |message, return_value|
      if m.respond_to?("#{message}=")
        m.__send__("#{message}=", return_value)
      else
        RSpec::Mocks.allow_message(m, message).and_return(return_value)
      end
    end

    yield m if block_given?
  end
end

Private Instance Methods

next_id() click to toggle source
# File lib/rspec/rails/mocks.rb, line 240
def next_id
  @@model_id += 1
end