Any suggestions on how to resolve this test error. The database has records which I try to fetch during test and run the test against. I am using minitest 5.3.3, rails 4.1.1 app and ruby 2.0.0p247
The test output and error thrown:
Finished in 0.017919s, 167.4200 runs/s, 55.8067 assertions/s.
1) Error:
Ff::BendRateTest#test_class_method:
NoMethodError: undefined method `rate' for nil:NilClass
Which is caused by this line that uses activerecord scopes to query the database. So I try to get the record nd then get the value of the rate from the fetched record:
d = Ff::BendRate.by_counter(letter_b)
The test class:
module Ff
class BendRateTest < ActiveSupport::TestCase
def test_class_method
m = Ff::BendRate.convert('2014-05-06', 10, 'us', 'gb')
assert_equal 5, m
end
end
end
The model is shown below:
module Ff
class BendRate < ActiveRecord::Base
scope :by_counter, -> (letter) {where(letter: letter) }
def self.convert(date, amount, letter_a, letter_b)
Bender.new(date, letter_a, letter_b).converter(amount)
end
end
end
The Bender class that we instantiate in the model above:
class Bender
def initialize(date = Date.today, letter_a, letter_b)
#letter_a = letter_a
#letter_b = letter_b
#date = date
end
def attempter
baseup(#letter_a, #date)
counterup(#letter_b, #date)
end
def converter(amount = 10)
#rate = attempter
(#rate * amount.to_f).round(4)
end
private
def counterup(letter_b, date)
d = Ff::BendRate.by_counter(letter_b)
e = d.first
#counteracting = e.rate.to_f
#counter_up_rate = (#counteracting / #baserater).round(4)
end
def baseup (leter_a, date)
a = Ff::BendRate.by_counter(letter_a)
b = a.first
#baserater = b.rate.to_f
#base_to_base = ( #baserater / #baserater).round(4)
end
end
Your test do not show, why it should return 5 items, you do not have any code which insert manually or by fixtures. You should explicitly show in your tests that you added all needed records, and then check that your methods working correctly with that data.
Elsewhere my steps which I'd use to find the problem:
Write test for BendRate#converter - it should fail with the same error
Write test for Bender#attempter - it should fail with the same error, too
Write tests for scope BendRate.by_counter for two cases letter_a and letter_b - it will fail because you have not setup data
or cheater way:
def test_class_method
p Ff::BendRate.all
m = Ff::BendRate.convert('2014-05-06', 10, 'us', 'gb')
assert_equal 5, m
end
Related
I have a model Appointment that prohibit the object to be created using a past date or update if the field day is in the past.
class Appointment < ApplicationRecord
belongs_to :user
...
validate :not_past, on: [:create, :update]
private
...
def not_past
if day.past?
errors.add(:day, '...')
end
end
end
But I need to make a test file using RSpec to test if it really cannot be edited if the field day is a past date.
require 'rails_helper'
RSpec.describe Appointment, type: :model do
...
it 'Cannot be edited if the date has past' do
#user = User.last
r = Appointment.new
r.day = (Time.now - 2.days).strftime("%d/%m/%Y")
r.hour = "10:00"
r.description = "Some Description"
r.duration = 1.0
r.user = #user
r.save!
x = Appointment.last
x.description = "Other"
expect(x.save).to be_falsey
end
...
end
The trouble is, the test can't be accurate due to an error that prohibit the creation of an Appointment object with the past day.
What should I do to force, or even maybe make a fake object with a past date for I can finally test it?
You can use update_attribute which will skip validations.
it 'Cannot be edited if the date has past' do
#user = User.last
r = Appointment.new
r.day = (Time.now - 2.days).strftime("%d/%m/%Y")
r.hour = "10:00"
r.description = "Some Description"
r.duration = 1.0
r.user = #user
r.save!
x = Appointment.last
x.description = "Other"
r.update_attribute(:day, (Time.now - 2.days).strftime("%d/%m/%Y"))
expect(x.save).to be_falsey
end
Also you have a lot of noise in your test (data which is not asserted) which you should avoid by e.g. creating a helper function or using factories.
it 'Cannot be edited if the date has past' do
appointment = create_appointment
appointment.update_attribute(:day, (Time.now - 2.days).strftime("%d/%m/%Y"))
appointment.description = 'new'
assert(appointment.valid?).to eq false
end
def create_appointment
Appointment.create!(
day: Time.now.strftime("%d/%m/%Y"),
hour: '10:00',
description: 'description',
duration: 1.0,
user: User.last
)
end
Also you test for falsey which will also match nil values. What you want to do in this case is test for false with eq false.
Is there a way to test for the existence of a class method being called from another class method?
class AnimalRecord < ApplicationRecord
COLLAR_ID = AnimalCollar::ID
belongs_to :animal
serialize :json, JSON
scope :collar_id, -> { for_collar(COLLAR_ID) }
def self.current_record(animal)
animal_info = AnimalRecord.collar_id.first
calculate_nutrients(animal_info)
end
def self.calculate_nutrients(animal)
code result
end
end
I can test the current_record method from current_record. But what is the proper way to test the calculate_nutrients method?
I had this:
context "test record" do
before do
#animal = create(:animal... )
#animal_record = create(:animal_record...)
end
it "calls #calculate_nutrients" do
expected_response = responseHere
expect(AnimalRecord).to receive(:calculate_nutrients).and_return(expected_response)
AnimalRecord.current_record(#animal)
end
But I get an error that says this:
expected: 1 time with any arguments
received: 0 times with any arguments
I think you have do remove the line expect(AnimalRecord).to receive(:event) from the test example. Seems there is no such method defined on AnimalRecord, but you are trying to "expect" it.
I have refined the Enumerable module in a Rails controller as follows, adding one method that, as far as I know, doesn't already exist in Rails or the standard library:
module OrderedGroupBy
refine Enumerable do
def ordered_group_by(&block)
items = each
groups = []
prev = nil
loop do
begin
i = items.next
current = i.instance_eval(&block)
groups << [] if current != prev
prev = current
groups.last << i
rescue StopIteration
break
end
end
groups
end
end
end
using OrderedGroupBy
class TestController < ApplicationController
def test
numbers = ['one', 'three', 'seven', 'four', 'nine']
#groups = numbers.ordered_group_by(&:length)
end
end
Calling the controller action test results in a NoMethodError:
undefined method `ordered_group_by' for ["one", "three", "seven", "four", "nine"]:Array
To narrow down the problem, I created a standalone file with the same structure (without the Rails environment):
module OrderedGroupBy
refine Enumerable do
def ordered_group_by(&block)
items = each
groups = []
prev = nil
loop do
begin
i = items.next
current = i.instance_eval(&block)
groups << [] if current != prev
prev = current
groups.last << i
rescue StopIteration
break
end
end
groups
end
end
end
using OrderedGroupBy
class TestController < Object
def test
numbers = ['one', 'three', 'seven', 'four', 'nine']
#groups = numbers.ordered_group_by(&:length)
end
end
TestController.new.test
In this case, calling the test method has the expected result:
# => [["one"], ["three", "seven"], ["four", "nine"]]
Also, if I define the ordered_group_by method directly on Enumerable, without using refine, I can successfully call the method.
Is there some reason why this would not work as a refinement in the context of Rails, or is there something else I'm missing?
Edit: I'm using rails 5.2.0, and this is an empty project, with only rails new _ and rails generate controller Test test having been run.
I am a complete beginner to Ruby. I am working on Lesson 45 of Learn Ruby the Hard Way currently and am creating a game similar to Zork and Adventure.
I have created a structure where I am creating 'scenes' in different files and requiring all the scenes in one file where I have an engine/map that ensures if the current scene does not equal 'finished' that it runs 'X' scene's 'enter' method.
However I have two issues:
1) I keep getting a error saying 'Warning class variable access from top level'
2) Even though the script is running I get
ex45.rb:30:in `play': undefined method `enter' for nil:NilClass (NoMethodError) from ex45.rb:59:in
The following is all of my code from each file. My apologies if it's a long read, but I would love to know why I am getting these two errors and what I can do to fix them.
Ex45.rb:
require "./scene_one.rb"
require "./scene_two.rb"
require "./scene_three.rb"
##action = SceneOne.new
##action_two = SceneTwo.new
##action_three = SceneThree.new
class Engine
def initialize(scene_map)
#scene_map = scene_map
end
def play()
current_scene = #scene_map.opening_scene()
last_scene = #scene_map.next_scene('finished')
while current_scene != last_scene
next_scene_name = current_scene.enter()
current_scene = #scene_map.next_scene(next_scene_name)
end
current_scene.enter()
end
end
class Map
##scenes = {
'scene_one' => ##action,
'scene_two' => ##action_two,
'scene_three' => ##action_three
}
def initialize(start_scene)
#start_scene = start_scene
end
def next_scene(scene_name)
val = ##scenes[scene_name]
return val
end
def opening_scene()
return next_scene(#start_scene)
end
end
a_map = Map.new('scene_one')
a_game = Engine.new(a_map)
a_game.play()
scene_one.rb:
class SceneOne
def enter
puts "What is 1 + 2?"
print "> "
answer = $stdin.gets.chomp
if answer == "3"
puts "Good job"
return 'scene_two'
else
puts "try again"
test
end
end
end
scene_two.rb
class SceneTwo
def enter
puts "1 + 3?"
print "> "
action = $stdin.gets.chomp
if action == "4"
return 'scene_three'
else
puts "CANNOT COMPUTE"
end
end
end
scene_three.rb
class SceneThree
def enter
puts "This is scene three"
end
end
Thanks in advance!
Answer to your first question:
You need to move the class variable definitions inside your Map class to get rid of these warnings:
Ex45.rb:5: warning: class variable access from toplevel
Ex45.rb:6: warning: class variable access from toplevel
Ex45.rb:7: warning: class variable access from toplevel
So, your Map class would look like this:
class Map
##action = SceneOne.new
##action_two = SceneTwo.new
##action_three = SceneThree.new
##scenes = {
'scene_one' => ##action,
'scene_two' => ##action_two,
'scene_three' => ##action_three
}
def initialize(start_scene)
#start_scene = start_scene
end
def next_scene(scene_name)
val = ##scenes[scene_name]
return val
end
def opening_scene()
return next_scene(#start_scene)
end
end
To answer your 2nd question:
You are getting undefined method 'enter' for nil:NilClass (NoMethodError) because your current_scene becomes nil at some point and then you try to call: current_scene.enter() i.e. nil.enter and it fails with that error message.
To solve this problem, you have to make sure you always have some value in your current_scene i.e. make sure it's not nil.
I think, you can just remove current_scene.enter() line from the end of your play method in the Engine class. So, your Engine class will look like this:
class Engine
def initialize(scene_map)
#scene_map = scene_map
end
def play()
current_scene = #scene_map.opening_scene()
last_scene = #scene_map.next_scene('finished')
while current_scene != last_scene
next_scene_name = current_scene.enter()
current_scene = #scene_map.next_scene(next_scene_name)
end
# current_scene.enter()
end
end
And, you won't get that error anymore.
Just so you know:
##y = 20
p Object.class_variables
--output:--
1.rb:1: warning: class variable access from toplevel
[:##y]
And:
class Object
def self.y
##y
end
end
puts Object.y
--output:--
20
But:
class Dog
##y = "hello"
def self.y
##y
end
end
puts Dog.y #=>hello
puts Object.y #=>What do you think?
The output of the last line is the reason that class variables are not used in ruby. Instead of class variables, you should use what are known as class instance variables:
class Object
#y = 10 #class instance variable
def self.y
#y
end
end
puts Object.y
class Dog
#y = "hello"
def self.y
#y
end
end
puts Dog.y #=> hello
puts Object.y #=> 10
A class instance variable is just an #variable that is inside the class, but outside any def. And instead of there being one ##variable that is shared by all the subclasses, each subclass will have its own #variable.
Hey I am trying to make a method polymorphic so that it can access different models associations, but treat them the same
module Unitable
# Adds new units without duplication
def add_units(type, units = {})
if type.downcase == "army"
relationships = self.members
elsif type.downcase == "character"
relationships = self.owns
end
units.each do |name, amount|
unit = Unit.find_by(name: name)
if relationships.where(unit_id: unit.id).first.nil?
relationships.create(unit_id: unit.id, amount: amount) #<--- This is where the error occurs
else
relationship = relationships.find_by(unit_id: unit.id)
original_amount = relationship.amount
new_amount = amount + original_amount
relationship.update_attribute(:amount, new_amount)
end
end
end
end
This throws
ERROR: current transaction is aborted, commands ignored until end of transaction block
This is the root method
def gen_starting_units
credit = 9
#transaction do
units = Unit.tagged_with(["#{race}", "1"]).sample(2)
while credit > 0
unit = units.sample
if unit.cost <= credit
self.army.add_units("army", unit.name => 1)
credit -= unit.cost
end
end
#end
end