How to refactor RSpec tests for API - ruby

I've got a series of RSpec tests for a Sinatra based API, and would like to refactor them to make them a little simpler and reduce repetition.
Here's an example a test for a route:
describe 'post /sections with empty data' do
before do
params = {
:site_id => site.id,
:page_id => page.id,
}
post '/sections', params, #session
end
specify { last_response.status.should == 200 }
specify { json_response['id'].should_not be_nil }
specify { json_response['type'].should == default_section_type }
end
Each test will be using the same base URL, with the same session data, the only difference is the parameters, and what the responses should be. There's at least 4 tests (GET, POST, PUT, DELETE) per route, and usually more.
Is there a way of making these tests more manageable?

Without resorting to metaprogramimng, you can use nested describe blocks to override only the parameters you want:
describe "/sessions" do
before do
send(http_method, "/sessions", params, #session)
end
describe "with POST" do
let(:http_method) { :post }
describe "and empty data" do
let(:params) do
{ :site_id => site.id, :page_id => page.id }
end
specify { last_response.status.should == 200 }
specify { json_response['id'].should_not be_nil }
specify { json_response['type'].should == default_section_type }
end
describe "with non-empty data" do
let(:params) do
# relevant params
end
end
end
describe "with GET" do
let(:http_method) { :get }
# ...
end
end

Have no idea if this works but it can give you an idea of what you can do
describe ' /sections with empty data' do
before(:all) do
#params = {
:site_id => site.id,
:page_id => page.id,
}
end
after(:each) do
specify { last_response.status.should == 200 }
specify { json_response['id'].should_not be_nil }
specify { json_response['type'].should == default_section_type }
end
[:get, :post, :put, :delete].each do |http_method|
it "works with #{http_method}" do
send(http_method) '/sections', #params, #session
end
end
end
Update
Reading your question again made me realize that this is not what you actually asked for. If it doesn't help at all tell me so I delete it.

Related

overwrite let inside an it method to alter params send to subject

It's possible to overwrite a let value inside an spec? I wanted to be able to set subject and modify my params within each test, something like:
subject {
MyClass.new params
}
let(:params) { {} }
describe '#initialize' do
it 'should set new params' do
params = {a: 1}
expect{ subject }.to do_something
end
it 'should raise with string' do
params = 'string'
expect{ subject }.to raise_error
end
end
or what is the correct way to approach this? should I wrote my expect{} with the whole class name?
I would rewrite the specs like this:
subject { -> { MyClass.new(params) } } # Note: subject is a lambda
describe '#initialize' do
context 'with blank params' do
let(:params) { {} }
it { is_expected.to do_something }
end
context 'with string params' do
let(:params) { 'string' }
it { is_expected.to raise_error }
end
end
But - as max pleaner already said - in this simple example it would probably be more readable and maintainable to skip the subject and just use the MyClass.new ... call directly in the expectation

FactoryGirl Sequence not instantiating when creating an object with association

I have to write a few tests for a controller I have. I have 3 models : User, UserInfo and UserPaymentPreference. I am using FactoryGirl to make a UserPaymentPreference object like this:
factory :advertiser_payment_preference_sequence do
association :advertiser_user, factory: [:advertiser_user_sequence]
end
factory :advertiser_user_sequence, class: 'AdvertiserUser' do
deposit { rand(100..1000).round(2) }
sequence(:login) { |n| "#{Faker::Internet.domain_name}#{n}" }
user_type 'advertiser'
association :user_info, factory: :advertiser_user_info_sequence
trait :no_deposit do
deposit 0
end
end
factory :advertiser_user_info_sequence, class: 'AdvertiserUserInfo' do
sequence(:firstname) { |n| "#{Faker::Name.first_name}#{n}" }
sequence(:organization) { |n| "#{Faker::Company.name}#{n}" }
sequence(:phone) { |n| "#{Faker::PhoneNumber.cell_phone}#{n}" }
sequence(:fiscal_code) { |n| "#{Faker::Company.duns_number}#{n}" }
sequence(:address) { |n| "#{Faker::Address.street_address}#{n}" }
sequence(:city) { |n| "#{Faker::Address.city}#{n}" }
end
This works fine if I do something like FactoryGirl.build(:advertiser_user_info_sequence) it will create an object of type UserInfo with all the fields populated as they should be.
However when I try to make an AdvertiserPaymentPreference object by doing FactoryGirl.build(:advertiser_payment_preference_sequence) all the user_info fields will be "" instead of the value that it should have been created by the association.
describe 'Advertiser::Billing::SettingsController', type: :request do
let!(:campaign) { create :campaign }
let!(:user) { campaign.user }
let!(:user_info) { user.user_info }
let!(:advertiser_payment_preference) { create(:advertiser_payment_preference, advertiser_user_id: user.id)}
before :each do
login(user)
end
describe "PUT/PATCH /advertiser/billing/settings" do
context "it is a valid request" do
it "updates the resource" do
binding.pry
new_advertiser_payment_preference = build(:advertiser_payment_preference_sequence)
binding.pry
params = {}
params['user_info'] = to_api_advertiser_info(new_advertiser_user_info)
api_put '/advertiser/billing/profile', params
expect(response.status).to eq(200)
user_info.reload
expect(user_info.organization).to eq(new_advertiser_user_info.organization)
expect(user_info.phone).to eq(new_advertiser_user_info.phone)
expect(user_info.fiscal_code).to eq(new_advertiser_user_info.fiscal_code)
expect(user_info.country).to eq(new_advertiser_user_info.country)
expect(user_info.address).to eq(new_advertiser_user_info.address)
end
end
end
end
This is how the test for the controller looks like . When I call new_advertiser_payment_preference = build(:advertiser_payment_preference_sequence) I get an object user_info with "" on all fields that are made using advertser_user_info_sequence.

RSpec argument match array of hashes

I would like to use argument matchers provided by rspec to match an array of hashes. This is ideally the code I would like:
context 'logging stock levels' do
subject { double(:stock_logger, stock_updated: nil) }
let(:stock_importer) { described_class.new(logger: subject) }
before(:each) { stock_importer.import }
it { is_expected.to have_received(:stock_updated)
.with(array_including(hash_including('sku', 'count_on_hand'))) }
end
This errorred with an argument mismatch for me. The only working solution I can come up with is the following:
context 'logging stock levels' do
subject { double(:stock_logger, stock_updated: nil) }
let(:stock_importer) { described_class.new(logger: subject) }
before(:each) { stock_importer.import }
it do
is_expected.to have_received(:stock_updated) do |stock_levels|
expect(stock_levels).to include(include('sku', 'count_on_hand'))
end
end
end
Was I just doing something wrong?
With Rspec 3.3, This worked for me:
expect(<Object>).to receive(:method).with(
array_including(hash_including( {"id" => some_id} ))
)
Try array_including but with the hash keys. Untested, but perhaps something like:
it do
is_expected.to have_received(:stock_updated)
.with(include(include("sku"), include("bar")))
end

Nested RSpec Tests

I am following Michael Hartl's tutorial and came across this following codes that I am having trouble comprehending.
describe "index" do
let(:user) { FactoryGirl.create(:user) }
before(:each) do
sign_in user
visit users_path
end
it { should have_title('All users') }
it { should have_content('All users') }
describe "pagination" do
before(:all) { 30.times { FactoryGirl.create(:user) } }
after(:all) { User.delete_all }
it { should have_selector('div.pagination') }
it "should list each user" do
User.paginate(page: 1).each do |user|
expect(page).to have_selector('li', text: user.name)
end
end
end
end
My question is:
is this a NESTED TEST where the test block of Pagination runs inside Index Test block? in other words, the sequence of testing flow:
before(:each) outer block of signing in user and visiting user path is executed
then the inner block of 30.times { FactoryGirl.create(:user) is executed
then the inner block of it { should have_selector('div.pagination') } is executed
then the inner block of expect(page).to have_selector('li', text: user.name) is executed
thank you
Here's the flow for the above test:
The before(:each) block is executed before each of the following:
it { should have_title('All users') }
it { should have_content('All users') }
Then, the before(:each) is executed again, followed by the describe block, which executes:
before(:all) { 30.times { FactoryGirl.create(:user) } }
it { should have_selector('div.pagination') }
it "should list each user" do
User.paginate(page: 1).each do |user|
expect(page).to have_selector('li', text: user.name)
end
end
Finally, after(:all) { User.delete_all } is executed.
I hope this helps explain the flow.

RSpec method testing

I am trying to test some methods in my models. For example,
in my model
def name
self.first_name + " " + self.last_name
end
I want to test it but I cannot do. How can I test this method in my model_spec.rb file?
Something like this, perhaps?
describe YourModel do
subject { YourModel.new(first_name: "Some", last_name: "Guy) }
its(:first_name) { should eql "Some" }
its(:last_name) { should eql "Guy" }
its(:name) { should eql "Some Guy" }
end
You could also use =~ and a regular expression, but I find that a little noisy.

Resources