How to test config method in this module with Rspec 3 mocks?
module TestModule
class << self
attr_accessor :config
end
def self.config
#config ||= Config.new
end
class Config
attr_accessor :money_url
def initialize
#money_url = "https://example1.come"
end
end
end
I tried something like this:
describe "TestModule config" do
it "should have config instance" do
config = class_double("TestModule::Config")
obj = instance_double("TestModule")
allow(obj).to receive(:config).and_return(config)
obj.config
expect(obj.config).to eq(config)
end
end
It looks, that it doesn't works, why ?
Failures:
1) TestModule config should have config instance
Failure/Error: allow(obj).to receive(:config).and_return(config)
TestModule does not implement: config
# ./spec/config_spec.rb:41:in `block (2 levels) in '
I'd recommend testing the class directly with
describe "module config" do
it "has config" do
expect(TestModule.config).to be kind_of(TestModule::Config)
end
end
If you don't need the .config to be changed by an outside object, then there's no need to have the attr_accessor :config as the def self.config already defines a accessible .config method to TestModule. If you want to allow .config to be changed from the outside, then just having a attr_writer :config should suffice as the reader/getter is already defined as that method.
Moreover, if you already have your class open with class << self, then declaring the .config method inside that would make more sense as it would contain all class level definitions. Just drop the self. from the beginning of the declaration so it reads def config as you are already "inside" the class.
I believe you mixed up class_double and instance_double. Try switching them and see how it goes. (sent from mobile phone so forgive my brevity)
UPDATE: Now that I'm at a computer, I can dig into this a bit more. First, why are you stubbing the method you're testing? Aren't you trying to test that it returns an instance of the Config class? By stubbing the .config method, you're not really testing your method to prove that it does what you want it to do. I think you could really simplify this to something like:
RSpec.describe TestModule do
describe ".config" do
it "returns an Config instance" do
expect(TestModule.config).to be_a TestModule::Config
end
end
end
Related
Given that I have an abstract class which provides inherited functionality to subclasses:
class Superclass
class_attribute :_configuration_parameter
def self.configuration_parameter config
self._configuration_parameter = config
end
def results
unless #queried
execute
#queried = true
end
#results
end
private
# Execute uses the class instance config
def execute
#rows = DataSource.fetch self.class._configuration_parameter
#results = Results.new #rows, count
post_process
end
def post_process
#results.each do |row|
# mutate results
end
end
end
Which might be used by a subclass like this:
class Subclass < Superclass
configuration_parameter :foo
def subclass_method
end
end
I'm having a hard time writing RSpec to test the inherited and configured functionality without abusing the global namespace:
RSpec.describe Superclass do
let(:config_parameter) { :bar }
let(:test_subclass) do
# this feels like an anti-pattern, but the Class.new block scope
# doesn't contain config_parameter from the Rspec describe
$config_parameter = config_parameter
Class.new(Superclass) do
configuration_parameter $config_parameter
end
end
let(:test_instance) do
test_subclass.new
end
describe 'config parameter' do
it 'sets the class attribute' do
expect(test_subclass._configuration_parameter).to be(config_parameter)
end
end
describe 'execute' do
it 'fetches the data from the right place' do
expect(DataSource).to receive(:fetch).with(config_parameter)
instance.results
end
end
end
The real world superclass I'm mocking here has a few more configuration parameters and several other pieces of functionality which test reasonably well with this pattern.
Am I missing something obviously bad about the class or test design?
Thanks
I'm just going to jump to the most concrete part of your question, about how to avoid using a global variable to pass a local parameter to the dummy class instantiated in your spec.
Here's your spec code:
let(:test_subclass) do
# this feels like an anti-pattern, but the Class.new block scope
# doesn't contain config_parameter from the Rspec describe
$config_parameter = config_parameter
Class.new(Superclass) do
configuration_parameter $config_parameter
end
end
If you take the value returned from Class.new you can call configuration_parameter on that with the local value and avoid the global. Using tap does this with only a minor change to your existing code:
let(:test_subclass) do
Class.new(SuperClass).tap do |klass|
klass.configuration_parameter config_parameter
end
end
As to the more general question of how to test functionality inherited from a superclass, I think the general approach of creating a stub subclass and writing specs for that subclass is fine. I personally would make your _configuration_parameter class attribute private, and rather than testing that the configuration_parameter method actually sets the value, I'd instead focus on checking that the value is different from the superclass value. But I'm not sure that's in the scope of this question.
I'm writing an acts_as_thingy module, intended to be used as per
class TestThingy
include ActsAsThingy
acts_as_thingy :name
end
ActsAsThingy is defined as
module ActsAsThingy
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_thingy *attributes
attributes.each do |attribute|
define_method attribute do
"thingy - #{attribute.to_s}"
end
end
end
end
end
And tested as
describe ActsAsReadOnlyI18nLocalised do
let(:thingy) { TestThingy.new }
it 'has a name method' do
expect(thingy.name).to eq "thingy - name"
end
end
What happens however is that, when I run the rspec, ActsAsThingy's self.included method is never invoked, and rspec complains that there is no such method as acts_as_thingy.
I seem to be missing something entirely obvious, but just can't see it.
Why isn't the self.included method being called when I include ActsAsThingy in the class?
update
Stepping through with pry I can see that after the include ActsAsThingy, if I then look at self.included_modules it shows up as [ActsAsThingy, PP::ObjectMixin, Kernel] So the include is working, it's not a paths issue or anything like that. The core question remains; why isn't self.included being invoked?
So after all that it turned out that I simply needed to add
require 'acts_as_thingy' to the top of the file that contained
class TestThingy
include ActsAsThingy
acts_as_thingy :name
end
I am not sure why Ruby didn't just throw an error when it couldn't find ActsAsThingy but it explains why the self.included method never got called (the include failed, but silently).
Check your scope.
Check your file require path.
Include is used for including methods into other
Modules
Require is what you want to use in your case.
Read up on the differences between require and include:
What is the difference between include and require in Ruby?
Here is my test code for your problem.
class A
require_relative 'test.rb'
end
p('test')
module Test
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def p(str)
print "#{str}"
end
end
end
output:
vagrant [test]> ruby file.rb
"test"
I have the following module and classes:
module MyModule
def self.included base
base.extend(ClassMethods)
end
module ClassMethods
attr_reader :config
# this method MUST be called by every class which includes MyModule
def configure &block
#config = {}
block.call(#config) if block
end
end
end
class A
include MyModule
configure do |config|
# do sth with the config
end
end
class B
include MyModule
end
Is it possible to check, if the configure method from the module was called? This means A should be fine, but B should throw an error, because it never called configure.
I tried it within the self.included callback, but the configure method gets called afterwards.
Technically, #ndn is right, it could be called after the class has been evaluated. However, it sounds like what you want is to validate that the configure method has been called at some point within the class body definition (this will also allow any modules that have been included, to finish evaluating, so if a module include calls the configure method, it's all good as well).
The closest solution I've come up to address this situation can be found here:
https://github.com/jasonayre/trax_core/blob/master/lib/trax/core/abstract_methods.rb
The above code is an abstract methods implementation for ruby, which technically isn't what you're asking (you are talking about calling the method, abstract methods are about checking that a subclass defined it), but the same trick I used there could be applied.
Basically, I'm using ruby's trace point library to watch for the end of the class definition to hit, at which point it fires an event, I check whether the method was defined, and throw an error if not. So as long as you're calling configure from WITHIN your classes, a similar solution could work for you. Something like (not tested):
module MustConfigure
extend ::ActiveSupport::Concern
module ClassMethods
def inherited(subklass)
super(subklass)
subklass.class_attribute :_configured_was_called
subklass._configured_was_called = false
trace = ::TracePoint.new(:end) do |tracepoint|
if tracepoint.self == subklass #modules also trace end we only care about the class end
trace.disable
raise NotImplementedError.new("Must call configure") unless subklass._configured_was_called
end
end
trace.enable
subklass
end
def configure(&block)
self._configured_was_called = true
#do your thing
end
end
end
class A
include MustConfigure
end
class B < A
configure do
#dowhatever
end
end
class C < B
#will blow up here
end
Or, you could try using the InheritanceHooks module from my library and skip the manual tracepoint handling:
class BaseClass
include::Trax::Core::InheritanceHooks
after_inherited do
raise NotImplementedError unless self._configure_was_called
end
end
Note, although I am using this pattern in production at the moment, and everything works great on MRI, because tracepoint is a library built for debugging, there are some limitations when using jruby. (right now it breaks unless you pass the jruby debug flag) -- I opened an issue awhile back trying to get tracepoint added in without having to enable debug explicitly.
https://github.com/jruby/jruby/issues/3096
Here's an example based on your structure.
It checks at instantiation if configure has been called, and will work automatically with any class on which you prepended MyModule.
It checks at every instantiation if configure has been called, but it is just checking a boolean so it shouldn't have any performance impact.
I looked for a way to undefine a prepended method for a specific class but didn't find anything.
module MyModule
def self.prepended base
base.extend(ClassMethods)
end
module ClassMethods
attr_reader :config
def configured?
#configured
end
def configure &block
#configured = true
#config = {}
block.call(#config) if block
end
end
def initialize(*p)
klass = self.class
if klass.configured? then
super
else
raise "Please run #{klass}.configure before calling #{klass}.new"
end
end
end
class A
prepend MyModule
configure do |config|
config[:a] = true
puts "A has been configured with #{config}"
end
end
class B
prepend MyModule
end
A.new
puts "A has been instantiated"
puts
B.new
puts "B has been instantiated"
# =>
# A has been configured with {:a=>true}
# A has been instantiated
# check_module_class.rb:27:in `initialize': Please run B.configure before calling B.new (RuntimeError)
# from check_module_class.rb:50:in `new'
# from check_module_class.rb:50:in `<main>'
I'm trying to use ruby's forwardable module to make some variables in one class accessible to another class. However I am having some trouble doing this.
It seems that I'm able to 'forward' some variables within the self (first bit of code) but I'm unable to forward some variable within a class (second bit of code)
The following works:
require 'forwardable'
module ModuleName
#
class << self
attr_accessor :config
def run
#config = {hey: 'hi', jay: 'ji'}
puts "1) Config = #{config}"
end
end
#
class Start
extend Forwardable
def_delegators ModuleName, :config
def run
puts "2) Config = #{config}"
end
end
end
ModuleName.run
(ModuleName::Start.new).run
#=> 1) Config = {:hey=>"hi", :jay=>"ji"}
#=> 2) Config = {:hey=>"hi", :jay=>"ji"}
BUT this doesn't
require 'forwardable'
module ModuleName
#
class Data
attr_accessor :config
def run
#config = {hey: 'hi', jay: 'ji'}
puts "1) Config = #{config}"
end
end
#
class Start
extend Forwardable
def_delegators ModuleName::Data, :config
def run
puts "2) Config = #{config}"
end
end
end
(ModuleName::Data.new).run
(ModuleName::Start.new).run
#=> 1) Config = {:hey=>"hi", :jay=>"ji"}
#=> /Users/ismailm/Desktop/ex.rb:17:in `run': undefined method `config' for ModuleName::Data:Class (NoMethodError)
Can you help in fixing this part of the code...
Typically when you delegate between two classes, one object contains an instance of the other. The fact that you call (ModuleName::Data.new).run implies to me that is what you are trying to do, but are somehow missing the fact that you need an instance of the contained class to be stored somewhere in order to receive the call to :config
This variation of your second piece of code is closer to what I would expect to see in a delegation scenario:
require 'forwardable'
module ModuleName
#
class Data
attr_accessor :config
def initialize
#config = {hey: 'hi', jay: 'ji'}
end
def run
puts "1) Config = #{config}"
end
end
#
class Start
extend Forwardable
def initialize data_obj = Data.new()
#data = data_obj
end
def_delegators :#data, :config
def run
puts "2) Config = #{config}"
end
end
end
(ModuleName::Data.new).run
(ModuleName::Start.new).run
I changed the constructor to ModuleName::Start in order to show a common pattern, not used here. Namely, you often pass in the wrapped object, or even more commonly the params that allow you to construct a new one and assign it to the instance variable that you wish to delegate to.
A minor related change: In your original code, the value of #config was only set via calling :run, so delegating direct to :config won't read the value you expect in the test. It worked in the first version because #config was set in the first call to run on the module, and then read globally as a singleton method on the second delegated call. I have worked around that here by setting it in Datas constructor, but of course anything that sets the value of #config on the instance you are delegating to would work.
I'm testing a class level instance variable (and setters) in a gem using RSpec. I need to test the following:
The correct default value is provided if the setter is never used.
The variable can be successfully updated through the setters.
Obviously there is a run order issue here. If I change the values using the setters, I lose memory of what the default value was. I can save it to a variable before the setter test and then reset the value at the end, but that only protects me if all setter tests follow the same practice.
What is the best way to test the default value of the variable?
Here is a simple example:
class Foo
class << self
attr_accessor :items
end
#items = %w(foo bar baz) # Set the default
...
end
describe Foo do
it "should have a default" do
Foo.items.should eq(%w(foo bar baz))
end
it "should allow items to be added" do
Foo.items << "kittens"
Foo.items.include?("kittens").should eq(true)
end
end
class Foo
DEFAULT_ITEMS = %w(foo bar baz)
class << self
attr_accessor :items
end
#items = DEFAULT_ITEMS
end
describe Foo do
before(:each) { Foo.class_variable_set :#items, Foo::DEFAULT_ITEMS }
it "should have a default" do
Foo.items.should eq(Foo::DEFAULT_ITEMS)
end
it "should allow items to be added" do
Foo.items << "kittens"
Foo.items.include?("kittens").should eq(true)
end
end
Or maybe a better way is to reload the class
describe 'items' do
before(:each) do
Object.send(:remove_const, 'Foo')
load 'foo.rb'
end
end
If your class has internal states that you would like to test I find that using the class_variable_get a nice way of approaching this. This does not require you to expose any of the variables in the class, so the class can stay untouched.
it 'increases number by one' do
expect(YourClass.class_variable_get(:##number)).to equal(0)
YourClass.increase_by_one()
expect(YourClass.class_variable_get(:##number)).to equal(1)
end
I know this is not what you ask for in your question, but it is in the title, which got me here.
I found this question pursuing a slightly different problem -- clearing a cached class variable between rspec examples.
In a module, I have an expensive class config, which I cache like this:
module Thingamizer
def config
#config ||= compute_config_the_hard_way()
end
end
class Thing
extend Thingamizer
end
In my rspec tests of Thing, compute_config_the_hard_way was only called the first time. Subsequent calls used the cached version, even if I mock compute_config_the_hard_way to return different things in other tests.
I resolved this by clearing #config before each example:
before { Thing.instance_variable_set(:#config, nil) }
Now the the thing I was hung up on is that #config is a class variable, not an instance variable. I tried many variations of class_variable_set without luck.
The wrinkle here is that Thing (the class) is actually an instance of Class. So what seems to be a class variable in a class method is actually an instance variable, in an instance of Class (i.e. Thing). Once I wrapped my head around that idea, using instance_variable_set instead of class_variable_set made perfect sense.
See Using Instance Variables in Class Methods - Ruby for a discussion of class variables as instance variables.