NoMethodError when running RSpec test - ruby-on-rails

I am getting the error NoMethodError when trying to use RSpec for testing the gem I created.
this is my gem:
~/project/gemz/spec/lib/checksomething.rb
class Checkpercentage
#1) what is x% of y?
def self.findAmount(rate, base)
resultAmount = rate * base/100
return resultAmount
end
#2) x is what percentage of y?
def self.findPercent(amount, base)
resultPercent = amount/base * 100
return resultPercent
end
#3) x is y% of what number?
def self.findBase(amount, rate)
resultBase = amount/rate *100
return resultBase
end
end # End Module
And this is my rspec test file:
./project/gemz/spec/lib/checkpercentage_spc.rb
require "spec_helper"
require "checksomething"
RSpec.describe Checkpercentage, '#intNum' do
context "with no integer entered" do
it "must be a number" do
checkpercentage = Checkpercentage.new
checkpercentage.findPercent(100, 200)
expect(checkpercentage.intNum) > 0
end
end
end
I want to test if the values enterend in the findPercentage method are > 0. However, when I run the rspec command in my terminal (rspec spec/lib/checkpercentage_spc.rb) the following error comes up:
Failures:
1) Checkpercentage#intNum with no integer entered must be a number
Failure/Error: checkpercentage.findPercent(100, 200)
NoMethodError: undefined method `findPercent' for #<Checkpercentage:0x9956ca4>
# ./spec/lib/checkpercentage_spc.rb:8:in `block (3 levels) in <top (required)>'
Finished in 0.00089 seconds (files took 0.17697 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/lib/checkpercentage_spc.rb:6 # Checkpercentage#intNum with no integer entered must be a number
I'm fairly new at ruby on rails. Can anyone point me to the right direction? Any help is appreciated.

A couple of things:
As Santhosh wrote, the way you have declared your methods (using self) makes all of them methods for the Class (Checkpercentage) itself. If you want them to be called on instance (Checkpercentage.new) you have to remove self from the declaration.
What is intNum (looks like Java but it doesn't exist in Ruby)? If I understand correct you want to check that findPercent(amount, base) returns a positive number. In that case, the right RSpec syntax is expect(checkpercentage.findPercent(100, 200)).to be > 0.
In Ruby a) camelCase is avoided in favour of camel_case and b) methods return the result of the last line that was executed. These mean you can rewrite your code as follows:
class Checkpercentage
#1) what is x% of y?
def find_amount(rate, base)
rate * base/100
end
#2) x is what percentage of y?
def find_percent(amount, base)
amount/base * 100
end
#3) x is y% of what number?
def find_base(amount, rate)
amount/rate * 100
end
end # end Module
notice that I have removed the self keywords to show you what I meant with my first point - now the syntax in the test you have written (checkpercentage.method_name) will be correct
Notice, furthermore, that there is a bug in your code - it works but not as you want it too. Hopefully the tests you'll write will help you find it and fix it, if not let us know!

Related

How is something like 30.seconds.ago implemented?

I found this question here.
And really curious to know the technical explanation of how something like 30.seconds.ago is implemented in Rails.
Method chaining? Numeric usage as per:
http://api.rubyonrails.org/classes/Numeric.html#method-i-seconds .
What else?
Here is the implementation of the seconds:
def seconds
ActiveSupport::Duration.new(self, [[:seconds, self]])
end
And, here is the implementation of the ago:
# Calculates a new Time or Date that is as far in the past
# as this Duration represents.
def ago(time = ::Time.current)
sum(-1, time)
end
And, here is the implementation of the sum method that's used inside the ago:
def sum(sign, time = ::Time.current) #:nodoc:
parts.inject(time) do |t,(type,number)|
if t.acts_like?(:time) || t.acts_like?(:date)
if type == :seconds
t.since(sign * number)
else
t.advance(type => sign * number)
end
else
raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
end
end
end
To understand it fully, you should follow the method calls and look for their implementations in the Rails source code like I showed you just now.
One easy way to find a method definition inside Rails code base is to use source_location in your Rails console:
> 30.method(:seconds).source_location
# => ["/Users/rislam/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/core_ext/numeric/time.rb", 19]
> 30.seconds.method(:ago).source_location
# => ["/Users/rislam/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/duration.rb", 108]

Readable test names with minitest

I'm using MiniTest on a new Rails project and this is my first time really doing testing. When a test fails the message looks like this
1) Failure:
Category::when created#test_0002_must have a unique name [/home/caleb/workspace/buzz/test/models/category_test.rb:10]:
Expected: true
Actual: false
Can you change #test_0002_ to another string to make the error more readable? I know it's a minor issue, but this seems like something that should be supported.
# Example test
require 'test_helper'
describe Category do
describe 'when created' do
unique = false
it 'must not have a unique name' do
unique.must_equal false
end
it 'must have a unique name' do
unique.must_equal true
end
end
end
Well, there is a lot here to cover, so bear with me.
First, the test names are readable. And they are 100% accurate. When you use the spec DSL you are still creating test classes and test methods. In your case, you class is Category::when created and your test method is test_0002_must have a unique name. The # in between them is a very common Ruby idiom for an instance method on a class, which is what your test method is. When you use class or def you can't create classes or methods with spaces in them, but when you create them programmatically you can. When running your code Ruby doesn't care if there are spaces in them or not.
Second, we can affect the display of test class and method. That text comes from a call to Minitest::Test#to_s. Here is what that looks like:
def to_s # :nodoc:
return location if passed? and not skipped?
failures.map { |failure|
"#{failure.result_label}:\n#{self.location}:\n#{failure.message}\n"
}.join "\n"
end
When the test fails then more info is returned, including the reason for the failure. But the piece we care about is the location. Here is what that looks like:
def location
loc = " [#{self.failure.location}]" unless passed? or error?
"#{self.class}##{self.name}#{loc}"
end
Ah, better. On the last line you can clearly see it is printing the class and the method name. If the test is failing the location also includes the filename where the method is defined. Let's break those values out so they aren't inline:
def location
loc = " [#{self.failure.location}]" unless passed? or error?
test_class = self.class
test_name = self.name
"#{test_class}##{test_name}#{loc}"
end
Okay, a bit clearer. First the test class, then the #, then the test name, then the location if the test is not passing. Now that we have them broken out we can modify them a bit. Let's use / to separate the class namespaces and the test method:
def location
loc = " [#{self.failure.location}]" unless passed? or error?
test_class = self.class.to_s.gsub "::", " / "
test_name = self.name
"#{test_class} / #{test_name}#{loc}"
end
Great. Now let's remove the test_0002_ from the beginning of the test method. That is added by the spec DSL, and by removing it we can make it match the string passed to the it block:
def location
loc = " [#{self.failure.location}]" unless passed? or error?
test_class = self.class.to_s.gsub "::", " / "
test_name = self.name.to_s.gsub /\Atest_\d{4,}_/, ""
"#{test_class} / #{test_name}#{loc}"
end
Now, your test output will look like this:
1) Failure:
Category / when created / must have a unique name [/home/caleb/workspace/buzz/test/models/category_test.rb:10]:
Expected: true
Actual: false
Minitest is no different than any other Ruby library. The spec DSL is simply a thin wrapper for creating test classes and methods. You can alter the behavior of your test objects to work the way you want them to.
TL;DR Add the following to your test/test_helper.rb file:
class Minitest::Test
def location
loc = " [#{self.failure.location}]" unless passed? or error?
test_class = self.class.to_s.gsub "::", " / "
test_name = self.name.to_s.gsub /\Atest_\d{4,}_/, ""
"#{test_class} / #{test_name}#{loc}"
end
end

rspec should_receive is not working but expect is working

I have class like below
#bank.rb
class Bank
def transfer(customer1, customer2, amount_to_transfer)
if customer1.my_money >= amount_to_transfer
customer1.my_money -= amount_to_transfer
customer2.my_money += amount_to_transfer
else
return "Insufficient funds"
end
end
end
class Customer
attr_accessor :my_money
def initialize(amount)
self.my_money = amount
end
end
And my spec file looks as below:
#spec/bank_spec.rb
require './spec/spec_helper'
require './bank'
describe Bank do
context "#transfer" do
it "should return insufficient balance if transferred amount is greater than balance" do
customer1 = Customer.new(500)
customer2 = Customer.new(0)
customer1.stub(:my_money).and_return(1000)
customer2.stub(:my_money).and_return(0)
expect(Bank.new.transfer(customer1, customer2, 2000)).to eq("Insufficient funds")
expect(customer1).to have_received(:my_money) # This works
customer1.should_receive(:my_money) #throws error
end
end
end
As per https://relishapp.com/rspec/rspec-mocks/v/2-14/docs/message-expectations both expect and should_receive are same but expect is more readable than should_receive. But why it is failing? Thanks in advance.
place this line:
customer1.should_receive(:my_money)
before
expect(Bank.new.transfer(customer1, customer2, 2000)).to eq("Insufficient funds")
expect to have_received and should_receive have diffent meaning
expect to have_received passes if object already received expected method call while
should_receive passes only if object will receive expected method call in future (in scope of current testcase)
if you would write
expect(customer1).to receive(:my_money)
instead of
expect(customer1).to have_received(:my_money)
it would fail too. Unless you place it before the line which calls this method.

Ruby on Rails RSpec Compare Function Values

I have two function values that I'm trying to compare and make sure one is greater than the other, and I just cannot figure out how to do it in RSpec. One function is "uncompleted_tasks" and the other is "tasks.count", both of which are part of a User model. Here is what I have in RSpec. The subject is an instance of the User model and RSpec gives me the error, "undefined local variable or method 'ut' for # (NameError)", on the line "expect(ut).should be <= tc". What's going on?
describe "uncompleted tasks should be less than or equal to total task count" do
before do
ut = subject.uncompleted_tasks
tc = subject.tasks.count
end
expect(ut).should be <= tc
end
Check out this SO answer for further details, but basically local variables in RSpec are limited to their local scope, including before blocks. So, the variables defined in your before block aren't available in the test. I'd suggest using instance variables for this:
describe "uncompleted tasks" do
before do
#ut = subject.uncompleted_task
#tc = subject.tasks.count
end
it "should be less than or equal to total task count" do
expect(#ut).should be <= #tc
end
end
You need to use instance variables and your expect needs to be inside an it block. Like below:
describe "uncompleted tasks should be less than or equal to total task count" do
before do
#ut = subject.uncompleted_tasks
#tc = subject.tasks.count
end
it "something" do
expect(#ut).should be <= #tc
end
end

Ruby: Default values for define?

I have a question about define my main issue is I am a bit confused on how the parameters work for it.
This is my Methods
def repeat(repeated_word)
#repeated_word = repeated_word
"##repeated_word ##repeated_word"
end
This is my rspec test to make sure my method works.
describe "repeat" do
it "should repeat" do
repeat("hello").should == "hello hello"
end
# Wait a second! How can you make the "repeat" method
# take one *or* two arguments?
#
# Hint: *default values*
it "should repeat a number of times" do
repeat("hello", 3).should == "hello hello hello"
end
end
it passes the first test but fails the second. My confusion is if i add a second parameter meaning def repeat(repeat_word, times_repeated)
the first test then fails because it has the wrong number of arguments. Not sure how to set up default values?
def repeat(repeated_word, repeats=2)
repeats.times.map { repeated_word }.join(' ')
end

Resources