How do I parameterise RSpec tests so I can test the same behaviour under slightly different conditions - ruby

I'm implementing a service that has several different ways it can be accessed:
Using simple query parameters
With parameters encoded as a Javascript object
For some calls both GET and POST are supported, with POST being used when there is large amounts of data being sent to the service.
What's the best way to structure my RSpec tests to avoid unnecessarily repeating code, allowing me to run the same basic assertions each time?
I'm already using shared_examples to capture some comment tests for things like response code, mimetype, etc. But I'm wondering whether there are other options, particularly when I want to invoke the service using all request methods AND a range of expected inputs and outputs.

The way I would do it in this case is to specify the request as a lambda that performs it. That way I can refer to it in my shared specs and set a different one for each type of request.
I like using rspec describe blocks when its sets an expectation, in this case that a particular request method is used. The whole thing will look something like this:
describe FooController do
shared_examples_for "any request" do
it "assigns foo" do
#request.call
assigns[:foo].should == "bar"
end
it "does not change the number of bars" do
#request.should_not change(Bar, :count)
end
end
context "using GET" do
before do
#request = lambda { get "index" }
end
it_should_behave_like "any request"
end
end
An even cleaner way is to use the 'let' construct, although it may be a step too deep in rSpec magic for a novice:
describe FooController do
shared_examples_for "any request" do
it "assigns foo" do
request.call
assigns[:foo].should == "bar"
end
it "does not change the number of bars" do
request.should_not change(Bar, :count)
end
end
context "using GET" do
let(:request) { lambda { get "index" } }
it_should_behave_like "any request"
end
end

Related

Upgrading Rspec to Rspec 2.99, want to use let connection variable in before/after hooks

I have a head scratcher. When upgrading rspec I am getting:
DEPRECATION: let declaration `directory` accessed in an `after(:all)` hook
at:
`let` and `subject` declarations are not intended to be called
Now I understand that I cannot use let defined variables in before/after hooks. However, the methods that are used with my test suite uses a connection to preform some REST API action:
let {:connection} {user_base}
after(:all) do
connection.delete_folder
end
My question is this: Is there anyway of getting around this without making every connection an instance variable? I want to avoid calling connection variable each time I want to preform an action e.g.
before(:all) do
#connection = user_base
end
it "adds folder" do
#connection.add_folder
end
it "edits folder" do
#connection.edit_folder
end
I think RSpec wants you to run the block before each example instead of your once before all examples:
let(:connection) { user_base }
after do # short for `after(:each) do`
connection.delete_folder
end
it "adds folder" do
connection.add_folder
end
it "edits folder" do
connection.edit_folder
end

Problems with rspec scope in before blocks

I've searched for an answer to this but I just can't seem to figure out what's going wrong. I have an api client test that looks like the following:
module MyTests
describe '#update' do
# using a before(:all) block for setup
before(:all) do
#client1 = Client.new
#initial_payload_state = #client1.update.payload
end
context 'with a known starting payload' do
# The payload is some nasty nested json so I grab an existing one
# and then use a helper method to convert it to a full payload.
# Then I update the client with the new payload. I'm using before(:each)
# so I can get the client into this state for every test.
before(:each) do
#full_payload_state = helper_method(#initial_payload_state)
end
context 'alter_payload_1 works' do
# now that I have the payload in its full state I'd like to alter it to
# produce a certain output
before(:all) do
#new_payload_state = alter_payload_1(#full_payload_state)
end
# I now want to update the client with the altered payload and make sure
# it has the same data. The request and response bodies are formatted slightly
# differently in this case.
it 'works' do
#updated_payload_state = #client1.update(#new_payload_state)
expect(payloads_equal?(#full_payload_state, #new_payload_state).to eq true
end
end
context 'alter_payload_2 works' do
before(:all) do
#new_payload_state = alter_payload_2(#full_payload_state)
end
it 'works' do
#updated_payload_state = #client1.update(#new_payload_state)
expect(payloads_equal?(#full_payload_state, #new_payload_state).to eq true
end
end
In reality, my before block for setup is much longer, so I think it makes sense to keep it that way. I tried to use a before(:each) block so I could have the same known state to start each of the alter_payload contexts. The problem is that with this setup, I get a no method error for this line:
#new_payload_state = alter_payload_1(#full_payload_state)
suggesting that #full_payload_state is nil. I'm certain I've got something wrong with respect to scope, but I'm not sure why or how to fix it. Any help greatly appreciated!
Looks like a scope issue with before(:all).
In general, it's wise to stop using before(:all) because it entangles your tests.
Replace your before(:all) lines with before(:each), and this will make each of your tests independent of the others. This will likely help you find your glitch.

What is this notation in rspec: it { is_expected.to ... }

I am reading the 'Better specs' page, and in one of the examples it says:
context 'when logged in' do
it { is_expected.to respond_with 200 }
end
context 'when logged out' do
it { is_expected.to respond_with 401 }
end
And I don't recognize this. I usually would do:
context 'when logged out' do
it 'responds with a 401' do
expect(response).to eq(401)
end
end
What is that syntax?
This is something introduced heavily in Rspec 3.XX. It's under the one line syntax guides as outlined here
RSpec supports a one-liner syntax for setting an expectation on the
subject. RSpec will give the examples a doc string that is auto-
generated from the matcher used in the example. This is designed
specifically to help avoid duplication in situations where the doc
string and the matcher used in the example mirror each other exactly.
When used excessively, it can produce documentation output that does
not read well or contribute to understanding the object you are
describing.
This comes in two flavors:
is_expected is defined simply as expect(subject) and is designed for
when you are using rspec-expectations with its newer expect-based
syntax.
it { is_expected.to respond_with 200 }
that is more readable.
Why you added description if you can read it from test.
Your code should be simple, smart and readable in the same time...
but if you realy want, you can add even novel... up to you :)

Testing a block with RSpec

I have some code (which I've stripped back for the purposes of the this example) and I'm looking for some advice on the best way to test it with RSpec.
ActionController::Renderers.add :liquid do |obj, options|
# Set the mime type as HTML.
self.content_type ||= Mime::HTML
# Do some clever stuff....
# Render the liquid layout.
self.response_body = layout.render(obj)
end
This block is added to a set through the add() call and executed at a later time.
I'm looking to test the functionality of the block, to ensure it does everything I want it too, such as setting the correct variables (self.content_type) and calling the correct libraries (layout.render).
I've never tested the content of a block like this, so looking some friendly advice on how best to go about it, can I test the block as-is, or does it need refactoring into an external method which can be tested?
You can stub out the addition of the Renderer, and test the call of the block from your code. In the code below I assume that the snippet you gave is inside a method named register_liquid:
let(:obj) { double(:obj) }
let(:options) { double(:options) }
before do
allow(ActionController::Renderers).to receive(:add).with(:liquid).and_yield(obj, options)
end
it 'sets content type' do
expect(subject).to receive(:content_type=).with(Mime::HTML)
subject.register_liquid
end

Alternative to rspec double that does not fail test even if allow receive is not specified for a function

Many times one outcome may have two different consequences that need to be tested with a test double. For example if a network connection is successful I'd like to log a message, and also pass the resource to another object that will store it internally. On the other hand it feels unclean to put these two in one test. For example this code fails:
describe SomeClass do
let(:logger) { double('Logger') }
let(:registry) { double('Registry') }
let(:cut) { SomeClass.new }
let(:player) { Player.new }
describe "#connect" do
context "connection is successful" do
it "should log info" do
logger.should_receive(:info).with('Player connected successfully')
cut.connect player
end
it "should register player" do
registry.should_receive(:register).with(player)
cut.connect player
end
end
end
end
I could specify in each test that the function in the other one might get called, but that looks like unnecessary duplication. In that case I'd rather make this one test.
I also don't like that it's never explicit in the test that a method should NOT be called.
Does anyone know about an alternative that has an explicit 'should_not_receive' message instead of automatically rejecting calls that are not explicitly specified?
RSpec supports should_not_receive, which is equivalent to should_receive(...).exactly(0).times as discussed in this message from the original author of RSpec.

Resources