models/message.rb
class Message
attr_reader :bundle_id, :order_id, :order_number, :event
def initialize(message)
hash = message
#bundle_id = hash[:payload][:bundle_id]
#order_id = hash[:payload][:order_id]
#order_number = hash[:payload][:order_number]
#event = hash[:concern]
end
end
spec/models/message_spec.rb
require 'spec_helper'
describe Message do
it 'should save the payload' do
payload = {:payload=>{:order_id=>138251, :order_number=>"AW116554416"}, :concern=>"order_create"}
message = FactoryGirl.build(:message, {:payload=>{:order_id=>138251, :order_number=>"AW116554416"}, :concern=>"order_create"})
message.event.should == "order_create"
end
end
error_log
Failures:
1) Message should save the payload
Failure/Error: message = FactoryGirl.build(:message, {:payload=>{:order_id=>138251, :order_number=>"AW116554416"}, :concern=>"order_create"})
ArgumentError:
wrong number of arguments (0 for 1)
# ./app/models/message.rb:4:in `initialize'
# ./spec/models/message_spec.rb:7:in `block (2 levels) in <top (required)>'
FactoryGirl requires you to define factory first. Let's say in a file spec/factories/messages.rb:
FactoryGirl.define do
factory :message do
bundle_id 1
order_id 2
...etc...
end
end
After this you'll be able to invoke factory build create like this:
FactoryGirl.build(:message) # => bundle_id == 1, order_id == 2
FactoryGirl.build(:message, order_id: 3) # => bundle_id == 1, order_id == 3
However, there is one problem in your particular case. FactoryGirl's default builders operate on top of ActiveRecord-alike interface. It sets defined attributes through setters, not through a hash of attrs passed to the model constructor:
m = Message.new
m.bundle_id = 1
m.order_id = 2
So you have to create a custom constructor to work with the interface of your model (which doesn't conform to ActiveRecord-alike model) and register it in your factory definition. See factory girl docs for details.
Let me show you an example of doing so. Sorry I didn't test it but it should give you a clue:
FactoryGirl.define do
factory :message do
ignore do
# need to make all attributes transient to avoid FactoryGirl calling setters after object initialization
bundle_id 1
order_id 2
end
initialize_with do
new(payload: attributes)
end
end
end
It's because you have a constructor with mandatory arguments. You have a few options;
1) Make the argument non-mandatory (although this would mean you're changing your code to suit your tests - naughty!)
def initialize(message = nil)
2) Use the "initialize_with" syntax in your factory;
describe Message do
it 'should save the payload' do
payload = {:payload=>{:order_id=>138251, :order_number=>"AW116554416"}, :concern=>"order_create"}
message = FactoryGirl.build(:message, {:payload=>{:order_id=>138251, :order_number=>"AW116554416"}, :concern=>"order_create"})
message.event.should == "order_create"
end
initialize_with { new(message) }
end
Related
I have problem while updating the application from Rails 4 to Rails 5.0.2
When I try I have this error:
/projects/tx/app/api/api_v2/validations.rb:3:in `<module:Validations>': uninitialized constant Grape::Validations::Validator (NameError)
from /projects/tx/app/api/api_v2/validations.rb:2:in `<module:APIv2>'
from /projects/tx/app/api/api_v2/validations.rb:1:in `<top (required)>'
from /projects/tx/app/api/api_v2/deposits.rb:1:in `require_relative'
from /projects/tx/app/api/api_v2/deposits.rb:1:in `<top (required)>'
Try to find solution for this but not success at all. Maybe Grape change some naming.
The code inside validations.rb seems like this:
module APIv2
module Validations
class Range < ::Grape::Validations::Validator
def initialize(attrs, options, required, scope)
#range = options
#required = required
super
end
def validate_param!(attr_name, params)
if (params[attr_name] || #required) && !#range.cover?(params[attr_name])
raise Grape::Exceptions::Validation, param: #scope.full_name(attr_name), message: "must be in range: #{#range}"
end
end
end
end
end
File deposits.rb is like this:
require_relative 'validations'
module APIv2
class Deposits < Grape::API
helpers ::APIv2::NamedParams
before { authenticate! }
desc 'Get your deposits history.'
params do
use :auth
optional :currency, type: String, values: Currency.all.map(&:code), desc: "Currency value contains #{Currency.all.map(&:code).join(',')}"
optional :limit, type: Integer, range: 1..100, default: 3, desc: "Set result limit."
optional :state, type: String, values: Deposit::STATES.map(&:to_s)
end
get "/deposits" do
deposits = current_user.deposits.limit(params[:limit]).recent
deposits = deposits.with_currency(params[:currency]) if params[:currency]
deposits = deposits.with_aasm_state(params[:state]) if params[:state].present?
present deposits, with: APIv2::Entities::Deposit
end
desc 'Get details of specific deposit.'
params do
use :auth
requires :txid
end
get "/deposit" do
deposit = current_user.deposits.find_by(txid: params[:txid])
raise DepositByTxidNotFoundError, params[:txid] unless deposit
present deposit, with: APIv2::Entities::Deposit
end
desc 'Where to deposit. The address field could be empty when a new address is generating (e.g. for bitcoin), you should try again later in that case.'
params do
use :auth
requires :currency, type: String, values: Currency.all.map(&:code), desc: "The account to which you want to deposit. Available values: #{Currency.all.map(&:code).join(', ')}"
end
get "/deposit_address" do
current_user.ac(params[:currency]).payment_address.to_json
end
end
end
You can find the reason here, and you can find initial method signature here.
change validations.rb
module APIv2
module Validations
class Range < ::Grape::Validations::Validator
def initialize(attrs, options, required, scope, opts = {})
#range = options
#required = required
super
end
def validate_param!(attr_name, params)
if (params[attr_name] || #required) && !#range.cover?(params[attr_name])
raise Grape::Exceptions::Validation, param: #scope.full_name(attr_name), message: "must be in range: #{#range}"
end
end
end
end
end
I try to test validation method that check times overlap for activities.
There are three factories(two of them inherit from activity).
Factories:
activities.rb
FactoryGirl.define do
factory :activity do
name 'Fit Girls'
description { Faker::Lorem.sentence(3, true, 4) }
active true
day_of_week 'Thusday'
start_on '12:00'
end_on '13:00'
pool_zone 'B'
max_people { Faker::Number.number(2) }
association :person, factory: :trainer
factory :first do
name 'Swim Cycle'
description 'Activity with water bicycles.'
active true
day_of_week 'Thusday'
start_on '11:30'
end_on '12:30'
end
factory :second do
name 'Aqua Crossfit'
description 'Water crossfit for evereyone.'
active true
day_of_week 'Thusday'
start_on '12:40'
end_on '13:40'
pool_zone 'C'
max_people '30'
end
end
end
Activities overlaps when are on same day_of_week(activity.day_of_week == first.day_of_week), on same pool_zone(activity.pool_zone == first.pool_zone) and times overlaps.
Validation method:
def not_overlapping_activity
overlapping_activity = Activity.where(day_of_week: day_of_week)
.where(pool_zone: pool_zone)
activities = Activity.where(id: id)
if activities.blank?
overlapping_activity.each do |oa|
if (start_on...end_on).overlaps?(oa.start_on...oa.end_on)
errors.add(:base, "In this time and pool_zone is another activity.")
end
end
else
overlapping_activity.where('id != :id', id: id).each do |oa|
if (start_on...end_on).overlaps?(oa.start_on...oa.end_on)
errors.add(:base, "In this time and pool_zone is another activity.")
end
end
end
end
I wrote rspec test, but unfortunatelly invalid checks.
describe Activity, 'methods' do
subject { Activity }
describe '#not_overlapping_activity' do
let(:activity) { create(:activity) }
let(:first) { create(:first) }
it 'should have a valid factory' do
expect(create(:activity).errors).to be_empty
end
it 'should have a valid factory' do
expect(create(:first).errors).to be_empty
end
context 'when day_of_week, pool_zone are same and times overlap' do
it 'raises an error that times overlap' do
expect(activity.valid?).to be_truthy
expect(first.valid?).to be_falsey
expect(first.errors[:base].size).to eq 1
end
end
end
end
Return:
Failure/Error: expect(first.valid?).to be_falsey
expected: falsey value
got: true
I can't understand why it got true. First create(:activity) should be right, but next shouldn't be executed(overlapping).
I tried add expect(activity.valid?).to be truthy before expect(first.valid?..., but throws another error ActiveRecord::RecordInvalid. Could someone repair my test? I'm newbie with creation tests using RSpec.
UPDATE:
Solution for my problem is not create :first in test but build.
let(:first) { build(:first) }
This line on its own
let(:activity) { create(:activity) }
doesn't create an activity. It only creates an activity, when activity is actually called. Therefore you must call activity somewhere before running your test.
There are several ways to do so, for example a before block:
before { activity }
or you could use let! instead of just let.
I added two methods to the Date class and placed it in lib/core_ext, as follows:
class Date
def self.new_from_hash(hash)
Date.new flatten_date_array hash
end
private
def self.flatten_date_array(hash)
%w(1 2 3).map { |e| hash["date(#{e}i)"].to_i }
end
end
then created a test
require 'test_helper'
class DateTest < ActiveSupport::TestCase
test 'the truth' do
assert true
end
test 'can create regular Date' do
date = Date.new
assert date.acts_like_date?
end
test 'date from hash acts like date' do
hash = ['1i' => 2015, '2i'=> 'February', '3i' => 14]
date = Date.new_from_hash hash
assert date.acts_like_date?
end
end
Now I am getting an error that: Minitest::UnexpectedError: NoMethodError: undefined method 'flatten_date_array' for Date:Class
Did I define my method incorrectly or something? I've even tried moving flatten_date_array method inside new_from_hash and still got the error. I tried creating a test in MiniTest also and got the same error.
private doesn't work for class methods, and use self.
class Date
def self.new_from_hash(hash)
self.new self.flatten_date_array hash
end
def self.flatten_date_array(hash)
%w(1 2 3).map { |e| hash["date(#{e}i)"].to_i }
end
end
I'm currently trying to write an RSpec test for a validation method. This method is triggered when the record is updated, saved or created. Here is what I have so far:
product.rb (model)
class Product < ActiveRecord::Base
validate :single_product
# Detects if a product has more than one SKU when attempting to set the single product field as true
# The sku association needs to map an attribute block in order to count the number of records successfully
# The standard self.skus.count is performed using the record ID, which none of the SKUs currently have
#
# #return [boolean]
def single_product
if self.single && self.skus.map { |s| s.active }.count > 1
errors.add(:single, " product cannot be set if the product has more than one SKU.")
return false
end
end
end
products.rb (FactoryGirl test data)
FactoryGirl.define do
factory :product do
sequence(:name) { |n| "#{Faker::Lorem.word}#{Faker::Lorem.characters(8)}#{n}" }
meta_description { Faker::Lorem.characters(10) }
short_description { Faker::Lorem.characters(15) }
description { Faker::Lorem.characters(20) }
sku { Faker::Lorem.characters(5) }
sequence(:part_number) { |n| "GA#{n}" }
featured false
active false
sequence(:weighting) { |n| n }
single false
association :category
factory :product_skus do
after(:build) do |product, evaluator|
build_list(:sku, 3, product: product)
end
end
end
end
product_spec.rb (unit test)
require 'spec_helper'
describe Product do
describe "Setting a product as a single product" do
let!(:product) { build(:product_skus, single: true) }
context "when the product has more than one SKU" do
it "should raise an error" do
expect(product).to have(1).errors_on(:single)
end
end
end
end
As you can see from the singe_product method, I'm trying to trigger an error on the single attribute when the single attribute is set to true and the product has more than one associated SKU. However, when running the test the product has no associated SKUs and therefore fails the unit test shown above.
How do I build a record and generate associated SKUs which can be counted (e.g: product.skus.count) and validated before they are all created in FactoryGirl?
You could write this like
it 'should raise an error' do
product = build(:product_skus, single: true)
expect(product).not_to be_valid
end
I've wrote the follow example:
it "should assign #services containing all the current user services" do
customer = FactoryGirl.create(:user, fullname: "Iris Steensma")
sign_in customer
service = FactoryGirl.create(:service, user: customer)
puts "Service.count = #{Service.count}" # Service.count = 0
get :home
assigns[:services].should eq([service])
end
The action controller as:
def home
##services = curent_user.posted_services
#services = Service.all
end
And factories.rb contains:
FactoryGirl.define do
sequence :address do |n|
"Street #{n}"
end
factory :user do
fullname "Foo Bar"
email { "#{fullname.gsub(' ', '.').downcase}#example.com" if fullname }
password "secret"
end
factory :preference do
profile "customer"
user
end
factory :service do
status :pending
source_addr { generate(:address) }
target_addr { generate(:address) }
passenger "Mis Daysi"
start_at Time.now
offer 5
payment "cash"
user
end
end
Why Factory Girl can't create the Service record? The factory works fine in the test environment "rails c test"
Here is the rspec ouput:
Failures:
1) UsersController GET home should assign #services containing all the current user services
Failure/Error: assigns[:services].should eq([service])
expected: [#<Service:0x460d8ea #name="Service_1003">]
got: []
(compared using ==)
Diff:
## -1,2 +1,2 ##
-[#<Service:0x460d8ea #name="Service_1003">]
+[]
# ./spec/controllers/users_controller_spec.rb:26:in `block (3 levels) in <top (required)>'
Finished in 1.03 seconds
3 examples, 1 failure
I believe the correct syntax is:
assigns(:service).should eq([service])
According to the rspec documentation assigns[key] used to be the way to assign instance variables which looks a little like what's happening for you.
https://github.com/rspec/rspec-rails
First, the user: customer here is not needed, since you are using customer.services.create that already does that
service = customer.services.create(attributes_for(:service, user: customer))
Second, try this, after service = customer.services.create do something like
service.valid?
puts service.errors.inspect
puts service.user == customer
maybe you can also try
service = FactoryGirl.create(:service, :user => customer)
also, are you sure the association is defined on Service class?
doesn't
#<Service:0x57856f4 #name="Service_1003">
should be
#<Service:0x57856f4 #name="Service_1003" user="<your customer object">
?
how is your factory for service defined?