ruby-on-railsrspecassociationsshoulda

How to test associations in rspec using shoulda-matchers gem?


Hello I am learning to test rails application using rspec. I am testing bank application where transaction belongs to account. I am testing the Transaction model. It's code is as follows: Transaction.rb:

class Transaction < ApplicationRecord
      validates :amount, presence: true, numericality: {  only_integer: true,
                                                          greater_than: 0 }
      after_create :update_balance
      belongs_to :account

     def update_balance
       if transaction_type == 'debit'
         account.current_balance -= amount
       else
         account.current_balance += amount
       end
    end
end

And it's spec is as follows:

require 'rails_helper'
RSpec.describe Transaction, type: :model do
  it { should belong_to(:account)}

  subject {
    described_class.new(amount: 60, transaction_type: 'credit',
                        id: 1,
                        created_at: DateTime.now, updated_at: DateTime.now,
                        account_id: 1)
  }

  it 'is valid with valid attributes' do
    expect(subject).to be_valid
  end

  it 'is not valid without amount' do
    subject.amount = nil
    expect(subject).to_not be_valid
  end

  it 'is not valid without transaction type' do
    subject.transaction_type = nil
    expect(subject).to_not be_valid
  end

  it 'is not valid without created_at date' do
    subject.created_at = nil
    expect(subject).to_not be_valid
  end

  it 'is not valid without updated_at date' do
    subject.updated_at = nil
    expect(subject).to_not be_valid
  end

  it 'is not valid without transaction id' do
    subject.id = nil
    expect(subject).to_not be_valid
  end

  it 'is not valid without account id' do
    subject.id = nil
    expect(subject).to_not be_valid
  end
end

I used shoulda gem for association. However when I run this test, it throws error as ' account must exist', even though I have added the association.

Error:

.F......

Failures:

  1) Transaction is valid with valid attributes
     Failure/Error: expect(subject).to be_valid
       expected #<Transaction id: 1, transaction_type: "credit", amount: 0.6e2, created_at: "2018-11-13 10:33:13", updated_at: "2018-11-13 10:33:13", account_id: 1> to be valid, but got errors: Account must exist
     # ./spec/models/transaction_spec.rb:12:in `block (2 levels) in <top (required)>'

Finished in 0.02937 seconds (files took 0.77127 seconds to load)
8 examples, 1 failure

Can anybody please help to understand what is it I am doing wrong?

P.S: Transaction table has account_id column for the association.

Thanks inadvance.


Solution

  • In Rails 5, associations defined as belongs_to are required by default. So when you are checking expect(subject).to be_valid, the valid? call is returning false because you don't have an account set.

    Your approach to get your tests working will then depend on what you want your application's object model to look like.

    If a transaction can exist without an account, then set up your association that way:

    class Transaction < ApplicationRecord
      belongs_to :account, optional: true
    end
    

    However, I think it's more likely that a transaction without an account doesn't make sense. So your current test is actually right – when you're testing an accountless transaction, it's not valid.

    In this case, you can set up your test subject accordingly:

    RSpec.describe Transaction, type: :model do
      let(:account) { Account.new }
      subject {
        described_class.new( ..., account: account)
      }
      ...
    end