RSpec argument match array of hashes - ruby

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

Related

Creating a FactoryBot Hash model with complicated structure, including multiple nested attributes

I'm implementing a set of Cucumber driven API tests and stumbled into one specific model that has a few nested elements, like this:
factory :human_being, class: Hash do
human {
name {
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
}
age { rand(1..100) }
}
initialize_with { attributes }
end
The result I wanted to achieve should look like this:
{
:human => {
:name => {
:first_name => "mike",
:last_name => "blob"
},
:age => 16
}
}
I am invoking creation using FactoryBot.build(:human_being) and I get undefined method 'name' for #<FactoryBot::SyntaxRunner:0x00007fd640a39a80>
My env.rb has the World(FactoryBot::Syntax::Methods) / FactoryBot.find_definitions lines.
I've lurked through a few answers regarding the nested attributes / associations / traits but I didn't find a proper way to get what I want. Sorry if the post is duplicated and thanks in advance for help.
Okay, seems that I've found the solution right after posting the question. Currently the following model creates the Hash I described in the question:
factory :human_being, class: Hash do
human { {
name: {
first_name: Faker::Name.first_name,
last_name: Faker::Name.last_name,
},
age: rand(1..100),
sex: %w(male female other).sample
} }
initialize_with { attributes }
end
Although I'm not sure it is a completely correct answer from guidelines perspective, but at least it works for my exact case.
source (See 'Defining factories'): Because of the block syntax in Ruby, defining attributes as Hashes (for serialized/JSON columns, for example) requires two sets of curly brackets:
factory :program do
configuration { { auto_resolve: false, auto_define: true } }
end

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.

How to refactor RSpec tests for API

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.

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