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
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
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.
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
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.
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.