In rspec and similar testing frameworks, how does one test for the absence of a method?
I've just started fiddling with rspec and bacon (a simplified version of rspec.) I wanted to define test that would confirm that a class only allows read access to an instance variable. So I want a class that looks like:
class Alpha
attr_reader :readOnly
#... some methods
end
I am rather stumped:
it "will provide read-only access to the readOnly variable" do
# now what???
end
I don't see how the various types of provided test can test for the absence of the accessor method. I'm a noob in ruby and ruby testing so I'm probably missing something simple.
In Ruby, you can check if an object responds to a method with obj.respond_to?(:method_name), so with rspec, you can use:
Alpha.new.should_not respond_to(:readOnly=)
Alternatively, since classes could override the respond_to? method, you can be stricter and make sure that there is no assignment method by actually calling it and asserting that it raises:
expect { Alpha.new.readOnly = 'foo' }.to raise_error(NoMethodError)
See RSpec Expectations for reference.
I believe you're looking for respond_to?, as in some_object.respond_to? :some_method.
Related
I am new to Ruby and I saw methods defined like:
def method_one
puts "method 1"
end
class MyClass
method_one
def method_two
puts "method 2"
end
end
The way method_one is used reminds me of Python decorators.The output of
c = MyClass.new
c.method_two
is
method 1
method 2
I have been trying to search for more information about this syntax/language feature in the Ruby documentation on the web but I don't know what keywords to search for.
What this is thing called?
TL;DR
This code doesn't do what you think it does. Don't do stuff like this.
Ruby's Top-Level Object
Ruby lets you define methods outside a class. These methods exist on a top-level object, which you can (generally) treat as a sort of catch-all namespace. You can see various posts like What is the Ruby Top-Level? for more details, but you shouldn't really need to care.
In your original post, method_one is just a method defined in the top-level. It is therefore available to classes and methods nested within the top-level, such as MyClass.
Methods in Classes
Despite what you think, the following doesn't actually declare a :method_one class or instance method on MyClass:
class MyClass
method_one
def method_two; end
end
Instead, Ruby calls the top-level ::method_one during the definition of the class, but it never becomes a class method (e.g. MyClass::method_one) or an instance method (e.g. MyClass.new.method_one). There might be a few use cases for doing this (e.g. printing debugging information, test injection, etc.) but it's confusing, error-prone, and generally to be avoided unless you have a really strong use case for it.
Better Options
In general, when you see something like this outside an academic lesson, the programmer probably meant to do one of the following:
Extend a class.
Add a singleton method to a class.
Include a module in a class.
Set up a closure during class definition.
The last gets into murky areas of metaprogramming, at which point you should probably be looking at updating your class initializer, or passing Proc or lambda objects around instead. Ruby lets you do all sorts of weird and wonderful things, but that doesn't mean you should.
I think you're a little mislead; the output of:
c = MyClass.new
c.method_two
is
#<MyClass:0x007feda41acf18>
"method 2"
You're not going to see method one until the class is loaded or if you're in IRB you enter the last end statement.
I would suggest looking into ruby's initialize method.
I've stumbled upon the following piece of code in an Rspec test and I must say I more or less figured out what it does but I can't find relevant sources to prove it. Please point me to a gem or docs that describe:
describe SomeModule::Salesforce::Lead do
before do
SomeModule::Salesforce::Lead.any_instance.expects(:materialize)
end
...
end
It seems that for :each example in this spec it sets expectation on any instance of the class described above to receive a call to :materialize method AND it actually redefines the method to do nothing . The last part seems crucial because it avoids connecting to SalesForce in test environment but I can't find confirmation for this.
any_instance is documented under Working with Legacy code
You are correct in that it both sets an expectation and stubs out the original method on any given instance of a class.
Previous versions of RSpec accomplish this by monkeypatch the ruby core classes (Object and BaseObject)
RSpec 3 has a new syntax which does not rely on monkeypatching:
before do
expect_any_instance_of(SomeModule::Salesforce).to receive(:materialize)
end
Ok I've just found that I was looking in wrong sources, it doesn't come from RSpec but from Mocha Mock (expects and any_instance) http://gofreerange.com/mocha/docs/Mocha/Mock.html#expects-instance_method
Thanks #tomasz-pajor #https://stackoverflow.com/users/2928259/tomasz-pajor
I have a class structure that looks like this:
module MyModule
class MyOuterClass
class MyInnerClass
end
end
end
I'm trying to make sure that a variable was correctly instantiated as a MyInnerClass using Rspec. printing the type of the class, it was MyModule::MyOuterClass::MyInnerClass. However, if I try to run the line
expect{#instance_of_MyInnerClass}.to be_an_instance_of(MyModule::MyOuterClass::MyInnerClass)
I get the error "You must pass an argument rather than a block to use the provided matcher." Additionally, the classes are in another location, so I can't just check
[...] be_an_instance_of(MyInnerClass)
Rspec complains that MyInnerClass is an uninitialized constant. So, I would like to ask how to verify that a variable is an instance of MyInnerClass using RSpec.
Don't Pass a Block
Rspec 3.x uses an expect method rather than a block syntax (see RSpec 3 Expectations 3.0). To get your spec to pass, and clean it up, you can use the following:
module MyModule
class MyOuterClass
class MyInnerClass
end
end
end
describe MyModule::MyOuterClass::MyInnerClass do
it "is correctly instantiated" do
expect(subject).to be_an_instance_of MyModule::MyOuterClass::MyInnerClass
end
end
Note the use of the implicit subject, passed as an argument to #expect. You can certainly pass other local or instance variables instead, but in this case subject is already defined for you as MyModule::MyOuterClass::MyInnerClass.new.
Most of us are using the preferred Rspec syntax, so it would be:
expect(#instance_of_MyInnerClass).to be_a MyInnerClass
It is stated in rspec doc that I should use double method in order to create test double.
But I can see that it works perfectly ok even if I don't use double. Is there anything wrong with not using double? Also if I'm not using double how MyClass gets stub and other rspec methods? Are they available for all objects when running in rspec?
require 'spec_helper'
class MyClass
def self.run
new.execute
end
def execute
'foo'
end
end
describe MyClass do
it 'should stub instance method' do
obj = MyClass.new
obj.stub(:execute).and_return('bar')
obj.execute.should == 'bar'
end
it 'should stub class method' do
MyClass.stub(:run).and_return('baz')
MyClass.run.should == 'baz'
end
end
Edit: I just reread your question and realized I didn't quite answer it. Leaving my original answer because it's related, but here's your specific answer:
The reason you don't need a double is because you're stubbing class methods, rather than instance methods. double is only useful for dealing with instances of the class, not the class itself.
Old answer that explains double some more:
You should always use real classes instead of test doubles when you can. This will exercise more of your code and make your tests more comprehensive. Test doubles are used in situations where you can't or shouldn't use a real object. For example, if a class can't be instantiated without hitting an external resource (like a network or a database), or has a large number of dependencies, and you're just testing something that uses it, you might want to create a double and stub some methods on the double.
Here's a more specific example: let's say you are testing MyClass, but in order to instantiate MyClass, you need to pass in a FooLogger:
mylogger = FooLogger.new
myclass = MyClass.new logger: mylogger
If FooLogger.new opens a syslog socket and starts spamming it right away, every time you run your tests, you'll be logging. If you don't want to spam your logs during this test, you can instead create a double for FooLogger and stub out a method on it:
mylogger = double(FooLogger)
mylogger.stub(:log)
myclass = MyClass.new logger: mylogger
Because most well-designed classes can be instantiated without any side-effects, you can usually just use the real object instead of a double, and stub methods on that instead. There are other scenarios where classes have many dependencies that make them difficult to instantiate, and doubles are a way to get past the cruft and test the thing you really care about.
In my experience, needing to use a double is a code smell, but we often have to use classes that we can't easily change (e.g. from a gem), so it's a tool you might need from time to time.
With RSpec Mocks 3.0 the behaviour of doubles has changed. You now may verify doubles, which means "RSpec will check that the methods
being stubbed are actually present on the underlying object if it is available", but "no checking will happen if the underlying object or class is not defined".
Verifying doubles requests you to be specific about the double type (instance, class, object, dynamic class, partial). Here is an example from the RSpec Relish for an instance double:
RSpec.describe User, '#suspend!' do
it 'notifies the console' do
notifier = instance_double("ConsoleNotifier")
expect(notifier).to receive(:notify).with("suspended as")
user = User.new(notifier)
user.suspend!
end
end
Hello I have a trouble with Ruby unit testing, I'm new to it so some help would be lovely
class TestItem < Test::Unit::TestCase
def setUp
**#item**=Item.new('Food','Burger',120)
end
def testGetType
assert_equal(**#item**.getType,'Food')
end
end
Here the value of instance variable #item takes nil when I declare it in setUp() and use it in test functions! So I get an error like no method 'getType' for nil-class
But when I directly use it like assert_equal(Item.new('Food','Burger',120).getType,'Food'),it works fine.
Please point out to my mistakes, thanks in advance
The name of the setup method is setup, not setUp. In fact, you will never find a method called setUp in Ruby, since the standard Ruby style for method naming is snake_case, not camelCase. (The same applies to getType and testGetType, BTW. It should be get_type and test_get_type. Well, actually, in Ruby, getters aren't prefixed with get, so really it should be type and test_type. But note that in Ruby, all objects already have type method, although that is deprecated.)