Rspec let scoping - ruby

I believe I have a problem with rspec let and scoping. I can use the methods defined with let in examples (the "it" blocks), but not outside (the describe block where I did the let).
5 describe Connection do
8 let(:connection) { described_class.new(connection_settings) }
9
10 it_behaves_like "any connection", connection
24 end
When I try to run this spec, I get the error:
connection_spec.rb:10: undefined local
variable or method `connection' for
Class:0xae8e5b8 (NameError)
How can I pass the connection parameter to the it_behaves_like?

let() is supposed to be scoped to the example blocks and unusable elsewhere. You don't actually use let() as parameters. The reason it does not work with it_behaves_like as a parameter has to do with how let() gets defined. Each example group in Rspec defines a custom class. let() defines an instance method in that class. However, when you call it_behaves_like in that custom class, it is calling at the class level rather than from within an instance.
I've used let() like this:
shared_examples_for 'any connection' do
it 'should have valid connection' do
connection.valid?
end
end
describe Connection do
let(:connection) { Connection.new(settings) }
let(:settings) { { :blah => :foo } }
it_behaves_like 'any connection'
end
I've done something similar to bcobb's answer, though I rarely use shared_examples:
module SpecHelpers
module Connection
extend ActiveSupport::Concern
included do
let(:connection) { raise "You must override 'connection'" }
end
module ClassMethods
def expects_valid_connection
it "should be a valid connection" do
connection.should be_valid
end
end
end
end
end
describe Connection do
include SpecHelpers::Connection
let(:connection) { Connection.new }
expects_valid_connection
end
The definition of those shared examples are more verbose than using shared examples. I guess I find "it_behave_like" being more awkward than extending Rspec directly.
Obviously, you can add arguments to .expects_valid_connections
I wrote this to help a friend's rspec class: http://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html ...

Redacted -- completely whiffed on my first solution. Ho-Sheng Hsiao gave a great explanation as to why.
You can give it_behaves_like a block like so:
describe Connection do
it_behaves_like "any connection" do
let(:connection) { described_class.new(connection_settings) }
end
end

I've discovered that if you do not explicitly pass the parameter declared by let, it will be available in the shared example.
So:
describe Connection do
let(:connection) { described_class.new(connection_settings) }
it_behaves_like "any connection"
end
connection will be available in the shared example specs

I found what works for me:
describe Connection do
it_behaves_like "any connection", new.connection
# new.connection: because we're in the class context
# and let creates method in the instance context,
# instantiate a instance of whatever we're in
end

This works for me:
describe "numbers" do
shared_examples "a number" do |a_number|
let(:another_number) {
10
}
it "can be added to" do
(a_number + another_number).should be > a_number
end
it "can be subtracted from" do
(a_number - another_number).should be < a_number
end
end
describe "77" do
it_should_behave_like "a number", 77
end
describe "2" do
it_should_behave_like "a number", 2
end
end

Related

Testing with Rspec - The correct way

My weakest point when it comes to coding, is using TDD & BDD methods - I tend to just write code.. but it is something that I am trying to work on.
Could anyone point out the best way to go about the following problem:
Class1:
module TempMod
class MyClass
def initalize(config)
#config = config
end
def process(xml)
if react_upon? xml.something
puts 'yeah'
else
puts 'nah'
end
end
def react_upon?(xml_code)
#code here
end
end
end
So lets say I wanted to test this class, or build it from a TDD point of view so I write my tests:
describe TempMod::MyClass do
let(:config) {double}
let(:myclass) {TempMod::MyClass.new config}
context 'Given that the xml is something we react upon' do
it 'should check that it is valid' do
myclass.process '<some><xml>here</xml></some>'
end
it 'should output yea'
end
end
How do I test that it is calling the react_upon? method. Do I even want to see it is calling it?
Is the proper way to test it, to test all the functions like the react_upon? itself independently of the other functions?
This is properly the main thing that is most confusing me with this sort of testing. Am I testing the whole class, or just individually testing the functions, and not their interactions with the other functions in that class?
Also I realize the the react_upon? might not adhere to the Single responsibility principle and I would probably move that out to its own module/class which I could test using a stub.
If anyone can shed some light on this for me that would be awesome.
edit:
describe TempMod::MyClass do
let (:valid_planning_status_xml) {
'<StatusUpdate> <TitleId>2329</TitleId> <FromStatus>Proposed</FromStatus> <ToStatus>Confirmed</ToStatus> </StatusUpdate>'
}
let(:config) { double }
let(:status_resolver) { double }
subject(:message_processor) { TempMod::MyClass.new config, status_resolver }
context 'Given that the message XML is valid' do
it 'should check the context of the message' do
expect(message_processor.process valid_planning_status_xml).to call :check_me
end
context 'Given that the message is for a planning event update' do
it 'should call something' do
pending
end
end
context 'Given that the message is for a recording job update' do
end
context 'Given that the message is for a video title update' do
end
end
end
Your question confused me a bit is this what you are asking
module TempMod
class MyClass
def initalize(config)
#config = config
end
def process(xml)
react_upon?(xml.something) ? 'yeah' : 'nah'
end
def react_upon?(xml_code)
#code here
end
end
end
Then test like
describe TempMod::MyClass do
let(:config) {double}
let(:myclass) {TempMod::MyClass.new config}
context 'Given that the xml is something we react upon' do
it "should respond to react_upon?" do
expect(myclass).to respond_to(:react_upon?)
end
it "should react_upon? valid xml" do
expect(myclass.react_upon?(YOUR VALID REACTION GOES HERE)).to be_true
end
it "should not react_upon? invalid xml" do
expect(myclass.react_upon?(YOUR INVALID REACTION GOES HERE)).to be_false
end
it "should say 'yeah' if it is valid" do
expect(myclass.process('<some><xml>here</xml></some>')).to eq('yeah')
end
it "should say 'nah' if it is invalid" do
expect(myclass.process('<some><xml>here</some>')).to eq('nah')
end
it 'should check the context of the message' do
expect(myclass).to receive(:react_upon?).with('<some><xml>here</xml></some>')
myclass.process('<some><xml>here</xml></some>')
end
end
end
Right now your tests have no expectations so I added one that expects myclass to respiond_to the react_upon? method and another that expects myclass.process(xml) to respond with a String that equals yeah.

What's the difference between "include_examples" and "it_behaves_like"?

In RSpec, what's the difference between it_behaves_like and include_examples?
The documentation says:
include_examples — include(s) the examples in the current context
it_behaves_like "name" — include(s) the examples in a nested context
But what does this actually mean? Replacing one with the other doesn't seem to have an effect on whether my tests pass or fail. Is there a reason to prefer one over the other in some situations?
Also, are it_should_behave_like and it_behaves_like just synonyms?
You probably know how to use describe, context, it and specify to clearly communicate one aspect of your code. The nested context provided by it_behaves_like can be used to improve this communication with the reader.
I will base my example on the example given in the RSpec documentation for shared examples:
shared_examples "a collection" do
context "initialized with 3 items" do
it "says it has three items" do
# ...
end
end
end
describe Array do
it_behaves_like "a collection"
include_examples "a collection"
end
If you run RSpec with --format documentation you get the following output:
Array
behaves like a collection
initialized with 3 items
says it has three items
initialized with 3 items
says it has three items
So the difference is how the spec is read eg in case of a failure.
Which style you prefer is a question of aesthetics of how you like your specs to read. Furthermore you would suggest to always use the same style if you work in a team to improve consistency.
Also, are it_should_behave_like and it_behaves_like just synonyms?
Almost, the context is named differently. it should behave like ... vs behaves like .... Again a question of aesthetics.
There is a difference in case you pass parameters to the shared_examples.
It's explained very well in a warning in their doc:
WARNING: When you include parameterized examples in the current context multiple
times, you may override previous method definitions and last declaration wins.
So if you have this kind of shared example (or shared context)
RSpec.shared_examples "some example" do |parameter|
\# Same behavior is triggered also with either `def something; 'some value'; end`
\# or `define_method(:something) { 'some value' }`
let(:something) { parameter }
it "uses the given parameter" do
expect(something).to eq(parameter)
end
end
RSpec.describe SomeClass do
include_examples "some example", "parameter1"
include_examples "some example", "parameter2"
end
You're actually doing this (notice that first example will fail):
RSpec.describe SomeClass do
\# Reordered code for better understanding of what is happening
let(:something) { "parameter1" }
let(:something) { "parameter2" }
it "uses the given parameter" do
\# This example will fail because last let "wins"
expect(something).to eq("parameter1")
end
it "uses the given parameter" do
expect(something).to eq("parameter2")
end
end
To prevent this kind of subtle error a warning is emitted if you
declare multiple methods with the same name in the same context.
Should you get this warning the simplest solution is to replace
include_examples with it_behaves_like, in this way method overriding
is avoided because of the nested context created by it_behaves_like

Using implicit `subject` with `expect` in RSpec-2.11

With the new expect syntax in rspec-2.11, how is it possible to use the implicit subject? Is there a better way than explicitly referencing subject, like below?
describe User do
it 'is valid' do
expect(subject).to be_valid # <<< can `subject` be implicit?
end
end
If you configure RSpec to disable the should syntax, you can still use the old one-liner syntax, since that doesn't involve should being added to every object:
describe User do
it { should be_valid }
end
We briefly discussed an alternate one-liner syntax, but decided against it since it wasn't needed and we felt like it might add confusion. You can, however, easily add this yourself if you prefer how it reads:
RSpec.configure do |c|
c.alias_example_to :expect_it
end
RSpec::Core::MemoizedHelpers.module_eval do
alias to should
alias to_not should_not
end
With this in place, you could write this as:
describe User do
expect_it { to be_valid }
end
With Rspec 3.0 you can use is_expected as described here.
describe Array do
describe "when first created" do
# Rather than:
# it "should be empty" do
# subject.should be_empty
# end
it { should be_empty }
# or
it { is_expected.to be_empty }
end
end
One could use the new named subject syntax, although it's not implicit.
describe User do
subject(:author) { User.new }
it 'is valid' do
expect(author).to be_valid
end
end

Is it possible to access the subject of the surrounding context in Rspec?

The following code doesn't work, but it best show what I'm trying to achieve
context "this context describes the class" do
subject do
# described class is actually a module here
c = Class.new.extend(described_class)
c.some_method_that_has_been_added_through_extension
c
end
# ... testing the class itself here ...
context "instances of this class" do
subject do
# this doesn't work because it introduces a endless recursion bug
# which makes perfectly sense
subject.new
end
end
end
I also tried to use a local variable in the inner context that I initialized
with the subject, but no luck. Is there any way I can access the subject of a outer scope from within my subject definition in the inner scope?
Using #subject can sometimes cause trouble. It is "primarily intended" for use with the short-hand checks like #its.
It also can make example harder to read, as it can work to mask the name/intent of what you testing. Here's a blog post that David Chelimsky wrote on the topic of #subject and #let and their role in revealing intention: http://blog.davidchelimsky.net/blog/2012/05/13/spec-smell-explicit-use-of-subject/
Try using let, instead
https://www.relishapp.com/rspec/rspec-core/v/2-10/docs/helper-methods/let-and-let
Here is how I would most likely write it.
context "this context describes the class" do
let(:name_of_the_module) { Class.new.extend(described_class) }
before do
c.some_method_that_has_been_added_through_extension
end
# ... testing the class itself here ...
context "instances of this class" do
let(:better_name_that_describes_the_instance) { klass.new }
# ... test the instance
end
end
SIDENOTE
You might want to revisit whether you want to use subject at all. I prefer using #let in almost all cases. YMMV
Something that obviously works is using an instance variable in the inner context and initializing it not with the subject but subject.call instead. Subjects are Procs. Hence, my first approach didn't work.
context "instances of this class" do
klass = subject.call
subject { klass.new }
end
I have been looking for a solution to this, but for different reasons. When I test a method that could return a value or raise an error, I often have to repeat the subject in two contexts, once as a proc for raise_error and once normally.
What I discovered is that you can give subjects names, like lets. This let's you reference an named subject from an outer scope within a new subject. Here's an example:
describe 'do_some_math' do
let!(:calculator) { create(:calculator) }
# proc to be used with raise_error
subject(:do_some_math) {
-> { calculator.do_some_math(with, complicated, args) }
}
context 'when something breaks' do
it { is_expected.to raise_error } # ok
end
context 'when everything works' do
# here we call the named subject from the outer scope:
subject { do_some_math.call } # nice and DRY
it { is_expected.to be_a(Numeric) } # also ok!
end
end

How can I clear class variables between rspec tests in ruby

I have the following class:
I want to ensure the class url is only set once for all instances.
class DataFactory
##url = nil
def initialize()
begin
if ##url.nil?
Rails.logger.debug "Setting url"
##url = MY_CONFIG["my value"]
end
rescue Exception
raise DataFactoryError, "Error!"
end
end
end
I have two tests:
it "should log a message" do
APP_CONFIG = {"my value" => "test"}
Rails.stub(:logger).and_return(logger_mock)
logger_mock.should_receive(:debug).with "Setting url"
t = DataFactory.new
t = nil
end
it "should throw an exception" do
APP_CONFIG = nil
expect {
DataFactory.new
}.to raise_error(DataFactoryError, /Error!/)
end
The problem is the second test never throws an exception as the ##url class variable is still set from the first test when the second test runs.
Even though I have se the instance to nil at the end of the first test garbage collection has not cleared the memory before the second test runs:
Any ideas would be great!
I did hear you could possibly use Class.new but I am not sure how to go about this.
describe DataFactory
before(:each) { DataFactory.class_variable_set :##url, nil }
...
end
Here is an alternative to the accepted answer, which while wouldn't solve your particular example, I'm hoping it might help a few people with a question in the same vein. If the class in question doesn't specify a default value, and remains undefined until set, this seems to work:
describe DataFactory
before(:each) do
DataFactory.remove_class_variable :##url if DataFactory.class_variable_defined? :##url
end
...
end
Works for me with a class with something more like:
def initialize
##url ||= MY_CONFIG["my value"]
...
end

Resources