This is my first model RSpec test:
require 'spec_helper'
describe CashFlow do
context 'DB Fields' do
it { should have_db_column :amount_cents }
it { should have_db_column :amount_currency }
it { should have_db_column :user_id }
it { should have_db_column :target_date }
it { should have_db_column :created_at }
it { should have_db_column :updated_at }
end
context 'Associations' do
it { should belong_to :user }
it { should have_and_belong_to_many :tags }
end
context 'Validation' do
it { should validate_presence_of :amount_cents }
it { should validate_numericality_of :amount_cents }
it { should ensure_exclusion_of(:amount_cents).in_array([0,]) }
it { should validate_presence_of :amount_currency }
it { should validate_presence_of :target_date }
it { should validate_presence_of :user }
end
context 'Scopes' do
subject { CashFlow }
context 'Incoming/Outcoming flows' do
let(:incoming_cash_flows) { 4.times.map { create :cash_flow } }
let(:outcoming_cash_flows) { 4.times.map { create :cash_flow, negative: true } }
it 'should be able to return all and only incomings cash_flows' do
expect(subject.incoming).to include *incoming_cash_flows
expect(subject.incoming).not_to include *outcoming_cash_flows
end
it 'should be able to return all and only outcomings cash_flows' do
expect(subject.outcoming).to include *outcoming_cash_flows
expect(subject.outcoming).not_to include *incoming_cash_flows
end
end
context 'Past/Future flows' do
[-1, nil, 1].each do |num|
_threshold = num ? Date.today + num.year : nil
let(:threshold) { _threshold }
let(:future_cash_flows) { 4.times.map { create :cash_flow, threshold: threshold } }
let(:past_cash_flows) { 4.times.map { create :cash_flow, past: true, threshold: threshold } }
it "should be able to return all and only past cash_flows with #{_threshold || 'current date (default)'} threshold" do
expect(subject.past(threshold)).to include *past_cash_flows
expect(subject.past(threshold)).not_to include *future_cash_flows
end
it "should be able to return all and only future cash_flows with #{_threshold || 'current date (default)'} threshold" do
expect(subject.future(threshold)).to include *future_cash_flows
expect(subject.future(threshold)).not_to include *past_cash_flows
end
end
end
end
it 'should save specified currency' do
%w(GEL RUB USD EUR).each do |currency|
cash_flow_id = create(:cash_flow, currency: currency).id
cash_flow = CashFlow.find(cash_flow_id)
expect(cash_flow.amount.currency).to eq currency
end
end
end
Model:
class CashFlow < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :tags
monetize :amount_cents, with_model_currency: :amount_currency
validates_presence_of :user, :target_date, :amount_cents, :amount_currency
validates_exclusion_of :amount_cents, in: [0, ]
scope :incoming, -> { where 'amount_cents > 0' }
scope :outcoming, -> { where 'amount_cents < 0' }
scope :past, lambda { |date = nil | where 'target_date < ?', date || Date.today}
scope :future, lambda { |date = nil | where 'target_date > ?', date || Date.today}
end
Factory:
FactoryGirl.define do
factory :cash_flow do
association :user
ignore do
negative false
past false
threshold nil
currency 'USD'
end
after :build do |cash_flow, evaluator|
sign = evaluator.negative ? -1 : 1
cash_flow.amount = Money.new((rand(10000) + 1) * sign, evaluator.currency || currency)
sign = evaluator.past ? -1 : 1
cash_flow.target_date = (evaluator.threshold || Date.today) + ((rand(60 * 24 * 30) + 1) * sign).minutes
end
end
end
What can you tell me about its readability, flexibility and coverage fullness?
I use factory girl, shoulda and money gems.
2 Answers 2
First thing: congratulations on starting with rspec! Way to go.
My first remark would be to remove your three first contexts (Fields, Associations & Validations).
Why? Because those are not testing any parts of your code - they are testing ActiveModel functionalities. You want to test your code, not your librairies - at least, not without a very good reason to think that there is a problem there. This is time spent to write specs that do not provide any benefits.
On the other side, while your model don't have much logic yet, I like the fact that you tested your scopes - this is your logic, and it should be tested.
-
\$\begingroup\$ I would keep the validation tests. In terms of spec'ing a model, I'd say it's valid (no pun intended) to specify that it should perform certain validations. A model may well have an attribute that's not used anywhere in the business logic (and thus won't be tested indirectly), but its presence is required for external reasons. \$\endgroup\$Flambino– Flambino2014年03月21日 13:31:23 +00:00Commented Mar 21, 2014 at 13:31
-
\$\begingroup\$ I would test the validations - but not as "is there validations" but as "should refuse an amount without currency" with an appropriate fixture. Now agree this maybe a question of preferences. \$\endgroup\$Martin– Martin2014年03月21日 14:26:26 +00:00Commented Mar 21, 2014 at 14:26
-
1\$\begingroup\$ That's an even better way to go. But your answer didn't give an example of a spec that could replace or improve upon the
shoulda
tests (you did that in your comment just now, however), so it could be interpreted as simply "don't test validation" \$\endgroup\$Flambino– Flambino2014年03月21日 14:51:43 +00:00Commented Mar 21, 2014 at 14:51 -
\$\begingroup\$ You are right. I'll update my answer accordingly. Thanks! \$\endgroup\$Martin– Martin2014年03月21日 14:52:41 +00:00Commented Mar 21, 2014 at 14:52
-
\$\begingroup\$ Thank you guys, I myself thought that DB fields testing is excessive, but as for validation, in terms of TDD I think it should be done, especially with help of shoulda matchers, because they check is there a particular validation or not. \$\endgroup\$atomAltera– atomAltera2014年03月22日 04:03:15 +00:00Commented Mar 22, 2014 at 4:03
Sounds quite complete. I just do not like to have any logic in my tests that make it hard to debug, like you did when executed some test using array [-1, nil, 1] . Also make sense what first comment says, to do not test the framework, but do test your code.
-
\$\begingroup\$ I used loop (iterating through array) in place of 3 quite similar tests, is this bad approach? \$\endgroup\$atomAltera– atomAltera2014年03月23日 04:35:33 +00:00Commented Mar 23, 2014 at 4:35
-
\$\begingroup\$ I woundn't say bad, but just more complext. I just like my tests as simple as possible, that's why I don't like to use. Some books says that every test case should have only one assert, so when you iterate in a array you are creating multiple asserts. Let suppose you are considering a range of [-1, 0, 1], and that represents a negative, neutral and positive account balance, so I think is more clear if you create three different tests. \$\endgroup\$jlucasps– jlucasps2014年03月23日 06:05:12 +00:00Commented Mar 23, 2014 at 6:05
Explore related questions
See similar questions with these tags.