Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed YAML serialization of a stubbed object.

Previously, the mock proxy would be serialized when #to_yaml is called on a stubbed object.  The mock proxy includes a reference to a proc.  When the yaml string is deserialized with YAML.load, you'd get an "allocator undefined for Proc" TypeError.

This fix prevents the mock proxy from being serialized, so that the object is serialized the same as it would if it hadn't been stubbed.

Note that Marshaling also breaks when Marshal.dump is passed a stubbed object, with a "singleton can't be dumped" error.  I'm not yet sure how to fix this, so I've left a pending spec for it.
  • Loading branch information...
commit 79401b27dadacdff419231a58eb382e9d5dba1ad 1 parent e1e288b
Myron Marston myronmarston authored dchelimsky committed
1  lib/rspec/mocks/framework.rb
View
@@ -14,3 +14,4 @@
require 'rspec/mocks/errors'
require 'rspec/mocks/error_generator'
require 'rspec/mocks/space'
+require 'rspec/mocks/serialization'
13 lib/rspec/mocks/methods.rb
View
@@ -73,10 +73,15 @@ def null_object?
private
def __mock_proxy
- if Mock === self
- @mock_proxy ||= Proxy.new(self, @name, @options)
- else
- @mock_proxy ||= Proxy.new(self)
+ @mock_proxy ||= begin
+ mp = if Mock === self
+ Proxy.new(self, @name, @options)
+ else
+ Proxy.new(self)
+ end
+
+ Serialization.fix_for(self)
+ mp
end
end
24 lib/rspec/mocks/serialization.rb
View
@@ -0,0 +1,24 @@
+module RSpec
+ module Mocks
+ module Serialization
+ def self.fix_for(object)
+ object.extend(YAML) if defined?(::YAML)
+ end
+
+ module YAML
+ def to_yaml(*a)
+ return super(*a) unless instance_variable_defined?(:@mock_proxy)
+
+ mp = @mock_proxy
+ remove_instance_variable(:@mock_proxy)
+
+ begin
+ super(*a)
+ ensure
+ @mock_proxy = mp
+ end
+ end
+ end
+ end
+ end
+end
69 spec/rspec/mocks/serialization_spec.rb
View
@@ -0,0 +1,69 @@
+require 'spec_helper'
+require 'yaml'
+
+module RSpec
+ module Mocks
+ class SerializableStruct < Struct.new(:foo, :bar); end
+
+ describe Serialization do
+ def self.with_yaml_loaded(&block)
+ context 'with YAML loaded' do
+ module_eval(&block)
+ end
+ end
+
+ def self.without_yaml_loaded(&block)
+ context 'without YAML loaded' do
+ before(:each) do
+ # We can't really unload yaml, but we can fake it here...
+ @orig_yaml_constant = Object.send(:remove_const, :YAML)
+ Struct.class_eval do
+ alias __old_to_yaml to_yaml
+ undef to_yaml
+ end
+ end
+
+ module_eval(&block)
+
+ after(:each) do
+ Object.const_set(:YAML, @orig_yaml_constant)
+ Struct.class_eval do
+ alias to_yaml __old_to_yaml
+ undef __old_to_yaml
+ end
+ end
+ end
+ end
+
+ subject { SerializableStruct.new(7, "something") }
+
+ def set_stub
+ subject.stub(:bazz => 5)
+ end
+
+ with_yaml_loaded do
+ it 'serializes to yaml the same with and without stubbing, using #to_yaml' do
+ expect { set_stub }.to_not change { subject.to_yaml }
+ end
+
+ it 'serializes to yaml the same with and without stubbing, using YAML.dump' do
+ expect { set_stub }.to_not change { YAML.dump(subject) }
+ end
+ end
+
+ without_yaml_loaded do
+ it 'does not add #to_yaml to the stubbed object' do
+ subject.should_not respond_to(:to_yaml)
+ set_stub
+ subject.should_not respond_to(:to_yaml)
+ end
+ end
+
+ it 'marshals the same with and without stubbing' do
+ pending("not sure how to fix this yet") do
+ expect { set_stub }.to_not change { Marshal.dump(subject) }
+ end
+ end
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.