Using Rspec, how can I test a method which calls a method from another class in ruby - ruby

My Transaction class has a method 'balance' which calls a method 'balance_after' from my Account class. I would like to make a test where I can check the 'balance_after' method is being called with the 'balance' method. I am new to both ruby and testing, so I appreciate any guidance! I am using simple-cov to get my coverage and i'd like to hit 100% if possible. I hope I have provided enough information. Thanks in advance!
Failures:
1) Transaction#balance checks balance
Failure/Error: #account.balance_after(self)
NoMethodError:
undefined method `balance_after' for :account:Symbol
# ./lib/transaction.rb:11:in `balance'
# ./spec/transaction_spec.rb:19:in `block (3 levels) in <top (required)>'
Finished in 0.04089 seconds (files took 0.87223 seconds to load)
10 examples, 1 failure
Failed examples:
rspec ./spec/transaction_spec.rb:18 # Transaction#balance checks balance
COVERAGE: 98.55% -- 68/69 lines in 4 files
+----------+--------------------+-------+--------+---------+
| coverage | file | lines | missed | missing |
+----------+--------------------+-------+--------+---------+
| 90.91% | lib/transaction.rb | 11 | 1 | 16 |
+----------+--------------------+-------+--------+---------+
My Transaction class
class Transaction
attr_reader :amount, :account, :timestamp
def initialize(amount, account)
#amount = amount
#account = account
#timestamp = Time.now.strftime("%d/%m/%Y")
end
def balance
#account.balance_after(self)
end
def formate
#amount > 0 ? (puts "#{#timestamp}|| #{#amount} || || #{balance}")
: (puts "#{#timestamp}|| || #{#amount.abs} || #{balance}")
end
end
My Account class
require_relative 'transaction'
class Account
attr_reader :balance, :transactions
HEADER = 'date || credit || debit || balance'
def initialize
#balance = 0
#transactions = []
end
def deposit(amount)
#balance += amount
#transactions << Transaction.new(amount, self)
end
def withdraw(amount)
#balance -= amount
#transactions << Transaction.new(-amount, self)
end
def balance_after(transaction)
index = #transactions.find_index(transaction)
#transactions[0..index].map(&:amount).sum
end
def print_statement
puts HEADER
#transactions.reverse.map(&:formate)
end
end
My Transaction spec
require 'transaction'
describe Transaction do
let(:transaction) { Transaction.new(:amount, :account) }
let(:timestamp) { Time.now.strftime('%d/%m/%Y') }
describe '#initilalize' do
it 'validates class' do
expect(transaction).to be_a Transaction
end
it 'stores dates' do
expect(transaction.timestamp).to eq timestamp
end
end
describe '#balance' do
it 'checks balance' do
expect(transaction.balance).to eq true
end
end
end

You have to call the method correctly. Transaction.new takes an amount, presumably a number, and some sort of Account object which has balance_after defined. You've given it two Symbols.
describe Transaction do
let(:account) { Account.new }
let(:amount) { 1.23 }
let(:transaction) { Transaction.new(amount, account) }
let(:timestamp) { Time.now.strftime('%d/%m/%Y') }
Then check that it returns what you expect. balance does not return true or false, it returns a balance. Specifically, it's just a pass through to account.balance_after(transaction), so test that.
describe '#balance' do
it 'checks balance' do
expect(transaction.balance).to eq account.balance_after(transaction)
end
end
This might seem circular, but this is just an integration test. account.balance_after would be unit tested as part of Account's tests. There's no need to repeat those tests here.

Related

Why does changing the order of 'it' and 'subject' in RSpec change my test result?

The class being tested qa.rb contains the code:
class QA
def initialize(bugs: 0)
#bugs = bugs
end
def speak
"Hello!"
end
def happy?
#bugs > 0
end
def debug
#bugs = 0
end
end
The RSpec file qa_spec.rb contains the code:
require 'rspec'
require_relative 'qa'
RSpec.describe QA do
describe '#happy?' do
context 'when bugs are more than 0' do
it 'returns true' do
subject { described_class.new(bugs: 1) }
expect(subject).to be_happy
end
end
end
end
The test fails when I run it, and gives me this error:
PS C:\Users\Jobla\repos\TDD> rspec qa_spec.rb
F
Failures:
1) QA#happy? when bugs are more than 0 returns true
Failure/Error: expect(subject).to be_happy
expected `#<QA:0x2e0d640 #bugs=0>.happy?` to return true, got false
# ./qa_spec.rb:9:in `block (4 levels) in <top (required)>'
Finished in 0.02999 seconds (files took 0.16995 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./qa_spec.rb:7 # QA#happy? when bugs are more than 0 returns true
However, when I edit qa_spec.rb and I swap the it and subject lines, the test suddenly passes:
require 'rspec'
require_relative 'qa'
RSpec.describe QA do
describe '#happy?' do
context 'when bugs are more than 0' do
subject { described_class.new(bugs: 1) } #swapped with line below
it 'returns true' do #swapped with line above
expect(subject).to be_happy
end
end
end
end
Tests pass:
PS C:\Users\Jobla\repos\TDD> rspec qa_spec.rb
.
Finished in 0.01003 seconds (files took 0.17993 seconds to load)
1 example, 0 failures
Please could someone explain why does swapping the it and subject lines change the result of the test?
subject is designed to be set in context or describe block, but not in it.
If you do not set subject before it then subject would be set automatically by calling new without parameters on described_class. bugs will be set to default 0. After that, you call it with a block subject { described_class.new(bugs: 1) } inside it, it's the same as if you call described_class.new { described_class.new(bugs: 1) } because subject inside it is an instance of QA class.

Rspec not finding class methods

I'm writing some tests for my backend jobs and I'm having a weird issue with rspec not finding my methods.
I wrote a simple class & test to illustrate the issue :
app/interactors/tmp_test.rb :
class TmpTest
def call
a = 10
b = 5
b.substract_two
return a + b
end
def substract_two
c = self - 2
return c
end
end
spec/interactors/tmp_test.rb :
require 'rails_helper'
describe TmpTest do
context 'when doing the substraction' do
it 'return the correct number' do
expect(described_class.call).to eq(13)
end
end
end
output:
TmpTest
when doing the substraction
return the correct number (FAILED - 1)
Failures:
1) TmpTest when doing the substraction return the correct number
Failure/Error: expect(described_class.call).to eq(13)
NoMethodError:
undefined method `call' for TmpTest:Class
# ./spec/interactors/tmp_test.rb:6:in `block (3 levels) in <top (required)>'
Finished in 0.00177 seconds (files took 1.93 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/interactors/tmp_test.rb:5 # TmpTest when doing the substraction return the correct number
It's not a class method, it's an instance method. Your test should look like this:
describe TmpTest do
subject(:instance) { described_class.new }
context 'when doing the subtraction' do
it 'returns the correct number' do
expect(instance.call).to eq(13)
end
end
end
This is a complete mess. Corrected version with comments:
class TmpTest
def call
a = 10
b = 5
# b.substract_two # why do you call method of this class on b?!
a + subtract_two(b)
end
def substract_two(from)
from - 2
end
end
Also: don’t use return in the very last line of the method.

wrong number of arguments ruby rspec

I'm trying to write unit tests for my code using rspec. I keep getting a "wrong number of arguments" error:
class MyClass
attr_accessor :env, :company,:size, :role, :number_of_hosts,:visability
def initialize(env, company, size, role, number_of_hosts, visability)
#env, #company, #size, #role, #number_of_hosts, #visability = env, company, size, role, number_of_hosts, visability
end
end
And here are my tests:
require_relative "../lib/MyClass.rb"
describe MyClass do
it "has an environment" do
MyClass.new("environment").env.should respond_to :env
end
it "has a company" do
MyClass.new("company").company.should respond_to :company
end
...
When I run rspec I get:
1) MyClass has an environment
Failure/Error: MyClass.new("environment").env.should respond_to :env
ArgumentError:
wrong number of arguments (1 for 6)
# ./lib/MyClass.rb:4:in `initialize'
# ./spec/MyClass_spec.rb:5:in `new'
# ./spec/MyClass_spec.rb:5:in `block (2 levels) in <top (required)>'
...
What am I missing?
EDIT
Sergio helped thanks...however
Sergio's answer worked...although I still have a further question:
Given the Class:
class Team
attr_accessor :name, :players
def initialize(name, players = [])
raise Exception unless players.is_a? Array
#name = name
raise Exception if #name && has_bad_name
#players = players
end
def has_bad_name
list_of_words = %w{crappy bad lousy}
list_of_words - #name.downcase.split(" ") != list_of_words
end
def favored?
#players.include? "George Clooney"
end
end
and spec...
require_relative "../lib/team.rb"
describe Team do
it "has a name" do
Team.new("random name").should respond_to :name
end
it "has a list of players" do
Team.new("random name").players.should be_kind_of Array
end
...
The tests pass without the same error...(This works fine: Team.new("random name"))
Any explanation?
Here is the error MyClass.new("environment"). As you have written def initialize(env, company, size, role, number_of_hosts, visability). So you should pass 6 parameters when you are calling MyClass#new method. But in practice you pass only one which is "environment". Thus you got the legitimate error - wrong number of arguments (1 for 6).

How would I rspec/test an updated_at field without using sleep() in ruby?

How do i write my spec without using the sleep(1.second) method? When I remove the sleep then my tests break because they are returning the same time stamp?
I have the following class method:
def skip
qs = find_or_create_by(user_id: user_id)
qs.set_updated_at
qs.n_skip += 1
qs.save!
end
and following spec:
qs = skip(user.id)
sleep(1.second)
qs2 = skip(user.id)
qs.should_not be_nil
qs2.should_not be_nil
(qs.updated_at < qs2.updated_at).should be_true
I've used the Timecop gem in the past for doing time based testing.
require 'timecop'
require 'test/unit'
class MyTestCase < Test::Unit::TestCase
def test_mortgage_due_in_30_days
john = User.find(1)
john.sign_mortgage!
assert !john.mortgage_payment_due?
Timecop.travel(Time.now + 30.days) do
assert john.mortgage_payment_due?
end
end
end
So your example may look like:
qs = skip(user.id)
Timecop.travel(Time.now + 1.minute) do
qs2 = skip(user.id)
end
qs.should_not be_nil
qs2.should_not be_nil
(qs.updated_at < qs2.updated_at).should be_true
This also works well for rspec tests. In your Gemfile:
require 'timecop', group: :test
Then, for example, you can use rspec to test a named scope that gets model called queries in descending updated_at order:
require 'timecop'
require 'spec_helper'
describe Query do
# test the named scopes for ordering and searching
describe 'when a query is searched or sorted' do
before :each do
#query1 = create(:query)
Timecop.travel(Time.now + 1.minute) do
#query2 = create(:query)
end
Timecop.travel(Time.now + 2.minute) do
#query3 = create(:query)
end
end
it 'should be listed in descending updated_at order' do
#queries = Query.order_by_latest
#queries.first.should == #query3
#queries.last.should == #query1
end
end
end

How to extend DataMapper::Resource with custom method

I have following code:
module DataMapper
module Resource
##page_size = 25
attr_accessor :current_page
attr_accessor :next_page
attr_accessor :prev_page
def first_page?
#prev_page
end
def last_page?
#next_page
end
def self.paginate(page)
if(page && page.to_i > 0)
#current_page = page.to_i - 1
else
#current_page = 0
end
entites = self.all(:offset => #current_page * ##page_size, :limit => ##page_size + 1)
if #current_page > 0
#prev_page = #current_page
end
if entites.size == ##page_size + 1
entites.pop
#next_page = (#current_page || 1) + 2
end
entites
end
end
end
Then I have call of #paginate:
#photos = Photo.paginate(params[:page])
And getting following error:
application error
NoMethodError at /dashboard/photos/
undefined method `paginate' for Photo:Class
In Active record this concept works fine for me... I'am using JRuby for notice. What I'am doing wrong?
Andrew,
You can think of DataMapper::Resource as the instance (a row) and of DataMapper::Model as the class (a table). Now to alter the default capabilities at either the resource or the model level, you can either append inclusions or extensions to your model.
First you will need to wrap your #paginate method in a module. I've also added a probably useless #page method to show how to append to a resource in case you ever need to.
module Pagination
module ClassMethods
def paginate(page)
# ...
end
end
module InstanceMethods
def page
# ...
end
end
end
In your case, you want #paginate to be available on the model, so you would do:
DataMapper::Model.append_extensions(Pagination::ClassMethods)
If you find yourself in need of adding default capabilities to every resource, do:
DataMapper::Model.append_inclusions(Pagination::InstanceMethods)
Hope that helps!

Resources