I am trying to test if an instance variable saves input from user but I cant figure out how. I expect the test to pass but it fails and outputs expected "joe" got: "" to the terminal. Here is the method, I want to test:
def get_names
while Player.players < 2
while name.length <= 2
puts 'Each player must choose a name'
print 'Enter a name: '
#name = gets.chomp
end
unless name == ''
#player1 == '' ? #player1 = Player.new(name) : #player2 = Player.new(name)
name = ''
puts 'Name have been saved successfully'
end
end
end
And here is the rspec test suite:
describe Game do
subject(:game_names) { described_class.new }
describe '#get_names' do
context 'when a user inputs name' do
it 'saves in name instance variable' do
allow(game_names).to receive(:gets).and_return('joe')
name = game_names.instance_variable_get(:#name)
expect(name).to eq 'joe'
game_names.get_names
end
end
end
end
I gave tried mocking and stubbing the class, the method and the variable, but I can't get it to work. This is my first time writing tests.
Without knowing what the rest of your Game and Player classes look like. I would say that you may have an execution order problem in your tests. I am basing the following on the assumption that you might not have a #name instance variable in your initialize method.
So in your tests, you're stubbing the call to gets and returning 'joe' which is cool and exactly what you want. But the next line after that goes and gets the instance variable #name which doesn't exist in the class at that moment because the method which sets that instance variable up hasn't been called yet. And undeclared instance variables will always hold the value, nil.
So you're essentially comparing nil to 'joe' which will never pass.
What you want to do is move game_names.get_names under the allow followed by the instance_variable_get line, and lastly the expect assertion.
describe Game do
subject(:game_names) { described_class.new }
describe '#get_names' do
context 'when a user inputs name' do
it 'saves in name instance variable' do
allow(game_names).to receive(:gets).and_return('joe')
game_names.get_names
name = game_names.instance_variable_get(:#name)
expect(name).to eq 'joe'
end
end
end
end
The bug was in the name assignment to empty string after saving input, so I commented it out and the test passed.
Related
I have a method but doesn't have a parameter, for example
def goodbye
puts ""
puts "Thank you, #{name} for using our service."
puts "Good luck with your future trip!"
puts ""
end
How can I do rpsec testing with this method that doesn't have a parameter ?
Alex advised me using the solution:
expect(goodbye).to output(/#{name}/).to_stdout
so my rspec code is like this:
describe "goodbye" do
it "should return goodbye with a name as input" do
expect(goodbye).to output(/#{name}/).to_stdout
end
end
But I got this error msg:
F
Failures:
1) goodbye should return goodbye with a name as input
Failure/Error: expect(goodbye).to output(/#{name}/).to_stdout
`name` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). It is only available on an example group (e.g. a `describe` or `context` block).
# ./test2.rb:41:in `goodbye'
# ./spec/tcs_spec.rb:24:in `block (2 levels) in <top (required)>'
Finished in 0.00211 seconds (files took 0.07718 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/tcs_spec.rb:23 # goodbye should return goodbye with a name as input
name is an RSpec method. It can be used outside an it to print the name of a spec, like this:
RSpec.describe Greeter do
describe "#goodbye" do
puts "Spec name: #{name}"
end
end
Which will output this: Spec name: RSpec::ExampleGroups::Greeter::Goodbye
When your expectation calls /#{name}/, you are interpolating a variable called name into a regular expression that will match the value of the variable in the output of the method to_stdout.
And, since you didn't declare a variable named name, RSpec tried to call its own name method which cannot be executed inside the context of an it statement (as the error states).
So, in order to get your test working, you need to declare a variable named name. You can do that inside the it statement (name = "skyline"), or you can do that with a let statement. Here's what the latter looks like (along with a subject to create the object and call the goodbye method...
RSpec.describe Greeter do
describe "#goodbye" do
subject(:goodbye) { Greeter.new.goodbye }
let(:name) { "skylinerr" }
it "should return goodbye with a name as input" do
expect { goodbye }.to output(/#{name}/).to_stdout
end
end
end
Note that #Alex was close, but not quite right. We need to call goodbye from a block (inside curly braces), not as a value (inside parentheses), since we're checking the output of the method to_stdout rather than the return value of the goodbye method which is nil.
For completeness, here's the class I wrote to get this test to pass...
class Greeter
def goodbye
puts ""
puts "Thank you, #{name} for using our service."
puts "Good luck with your future trip!"
puts ""
end
def name
"skylinerr"
end
end
Sorry I'm a big beginner in Ruby and I don't know if my title and my question makes sense...
Here my code :
class Account
attr_reader :name, :balance
def initialize(name, balance=100)
#name = name
#balance = balance
end
def display_balance
pin_check
puts "Hello #{name} ! Your balance is $ #{balance}."
end
private
def pin
#pin = 1234
end
def pin_check(pin_number)
if #pin_number == #pin
puts "Access authorized: pending transaction"
else puts "Access denied: wrong PIN"
end
end
end
checking_account = Account.new("Saitama", 20_000)
checking_account.display_balance
What I’m trying to do is to automate the “pin check” in the other methods. My problem here is with the pin_check parameter : of course, the program is expecting an argument from this method but on my last line of code, I don’t know how to give it the 1234 argument expected… Is there any way to write correctly this last line of code to link display_balance and pin_check so as to giving the good argument to pin_check ?
I was thinking maybe write something like that but I know it doesn't work :
checking_account.display_balance.pin_check(1234)
How can I link both ? Thank you so much for your help !
I think the simplest thing to do would be to perform pin_check in the constructor, and then you don't have to worry about doing it for all methods. pin_number would be a constructor argument.
Another option would be to use something like ActiveModel::Validations, and add a validator that checks the PIN. Any client of your class would need to know to validate before performing any of the actions. That is the pattern used by Rails facilities such as ActiveRecord.
You can pass the pin into display_balance and pass it through to pin_check.
def display_balance(pin:)
pin_check(pin)
puts "Hello #{name} ! Your balance is $ #{balance}."
end
checking_account = Account.new("Saitama", 20_000)
checking_account.display_balance(pin: 1234)
Note that because pin_check only prints, the balance will always be displayed. You probably want it to raise an exception.
def pin_check(pin_number)
raise "Access denied: wrong PIN" unless pin_number == pin
end
Note that it's pin_number and not #pin_number. pin_number is the variable containing what was passed into pin_check. #pin_number is an "instance variable" stored on the object.
Update: Also note that it's pin and not #pin. #pin will not be set unless pin is called.
It's probably better to pass the pin in once during object initialization and raise an error if it does not match. This guarantees no operations can happen without a pin check.
attr_reader :name, :balance
def initialize(name:, balance: 100, pin:)
#name = name
#balance = balance
pin_check(pin)
end
private def pin_check(pin_try)
raise "Access denied: wrong PIN" unless pin_try == #pin
end
checking_account = Account.new(name: "Saitama", balance: 20_000, pin: 1234)
checking_account.display_balance
Note that I'm using named arguments. This lets you add arguments, even optional ones, without having to remember what argument 3 was.
You basically need to pass around pin wherever needs. You can pass pin to following method
def display_balance(pin)
pin_check(pin)
puts "Hello #{name} ! Your balance is $ #{balance}."
end
Now you can call checking_account.display_balance(1234)
I have following code, which creates instance variables dynamically. If the instance variable does not exist, I use the no method error to create the attr_reader and attr_writer methods dynamically. All works fine, but I don't understand why I don't see the #anything instance variable after having created the first attr_reader.
require "pry-byebug"
class UberHash
attr_accessor :color
def initialize
#color = nil
end
def method_missing(m, *args, &block)
if m.match(/=/)
puts "create attr writer"
self.instance_variable_set("##{m[0..-2]}", args[0])
else
puts "create attr reader"
self.instance_variable_get("##{m}")
end
end
end
uber_hash = UberHash.new
puts "call 1 .................."
p "uber_hash.anything: #{uber_hash.anything}"
p "uber_hash.inspect: #{uber_hash.inspect}"
p uber_hash.anything = "hola"
p uber_hash.inspect
With following results:
call 1 ..................
create attr reader
"uber_hash.anything: "
"uber_hash.inspect: #<UberHash:0x00007f851b1e41a8 #color=nil>"
"#<UberHash:0x00007f851b1e41a8 #color=nil>"
create attr writer
"hola"
"#<UberHash:0x00007f851b1e41a8 #color=nil, #anything=\"hola\">"
After creating the first instance variable anything, with the method instnace_variable_set, I understand that I create an attr_reader right?
Why do I not see the #anything instance variable if I inspect the instance?
You don't see the instance variable in the first inspect. You expect it the be there because in previous line you call uber_hash.anything, right?
Well, the uber_hash.anything call evaluates the else in the #method_missing conditional: self.instance_variable_get("##{m}") - that's why no instance variable is set.
Also, in #method_missing conditional you print two messages: puts "create attr writer" and puts "create attr reader" - they are wrong. It should be: puts "create instance variable" and puts "read instance variable"
After creating the first instance variable anything, with the method instnace_variable_set, I understand that I create an attr_reader right?
No, that is not correct. Your class never creates (or runs) attr_reader. Try this (after running your example commands)
p( uber_hash.methods - Object.new.methods )
and you see only the methods additionally defined in your class be [:color, :color=, :method_missing] in your class.
The method :color is defined because of attr_accessor :color. Remember attr_accessor etc is just a shortcut to define methods.
By contrast, the method :anything is not defined because your class has never defined the method.
Instead, in your class, every time a method uber_hash.anything is called, uber_hash.method_missing is run and does the job, that is, manipulation or viewing of the instance variable #anything.
Secondly, while instance_variable_set does set a value to an instance variable (and creates it if it does not exists), instance_variable_get refers to it only if it exists, else returns nil and does not create an instance variable. That is why #anything is created after instance_variable_set, but not just after instance_variable_get. Try this to see the point (after your definition of the class).
class UberHash
def test_pr
print 'color='; p instance_variable_get("#color")
print 'other='; p instance_variable_get("#other")
p instance_variables # => #other is not defined
print 'other='; p instance_variable_set("#other", 99)
p instance_variables # => #other is NOW defined
end
end
uber_hash.test_pr
Consequently, the behaviour you see is perfectly legitimate.
Note: this past answer explains it.
I am a ruby newbie, I have managed to pull out the code for ruby but writing rspecs for them seems problematic. It's hard to understand the way to write rspecs even after reading few tutorials. Someone please help me to write for an input method then I would try to refactor it for the rest.
RB file:
module RubyOperations
class Operations
def input(num)
RubyOperations.log('Enter a number:[Between 1 to 10]',:BOTH)
num = Integer(gets.chomp)
raise StandardError if num <= 0 || num > 10
return num
rescue StandardError, ArgumentError => e
RubyOperations.log(e,:ERROR)
end
end
end
RSPEC:
describe 'RubyOperations' do
describe 'Operations' do
describe '.input' do
context 'when number is provided' do
it 'returns the number provided' do
expect(RubyOperations.input(num)).to eq(Integer)
end
end
end
end
end
You can check the class of the output of the method to equal integer
require 'ruby_final_operations'
describe 'RubyOperations' do
describe 'Operations' do
describe '.input' do
context 'when number is provided' do
it 'returns the number provided' do
expect(RubyOperations.input(num).class).to eq(Integer)
(or)
expect(RubyOperations.input(num)).to be_a_kind_of(Integer)
end
end
end
end
end
And whenever you write rspec keep in mind
If the method for which you are writing rspec deals with manipulations in your db then check if db is manipulated or not
Or if you are writing rspec for any methods which returns an object then procced like this
if a method is defined like
def square_of_a_number(num)
num*num
end
Then write rspec like this
it 'returns square of a number' do
expect(square_of_a_number(2).to eq(4)
end
For any methods that you know the output of a method will be that then hardcode the input or user Faker gem for input of the method expect the expected result of that method
There are few issues with code that you have shared:
1) In the Operations class, the method input receives an argument which is not used anywhere because of this line: num = Integer(gets.chomp). Basically gets is the method that waits for user input, and the assignment num = ... overrides the value of argument (num) that is passed into the method, hence it is pointless to pass num argument into the method.
2) In your spec sample you call input method on RubyOperations module, while the input lives in class Operations under namespace RubyOperations. Also method input is not a class method but instance method. So proper method call would be: RubyOperations::Operations.new.input(5)
3) To run a spec for input method you would need to stub user input. RSpec-mocks gem can help you with that - https://github.com/rspec/rspec-mocks. It has allow stub method: allow(object).to receive(:gets) { "5" }
The whole sample will be:
it 'returns the number provided' do
# instantiate object that we will test
subject = RubyOperations::Operations.new
# we stub method 'gets' and whenever it is called we return string "5"
allow(subject).to receive(:gets) { "5" }
# we call method input with argument 1, the argument 1 is pointless as described in point 1) and can be omitted
expect(subject.input(1)).to eq(5)
end
Here're the codes from about_classes.rb. I'm not very sure about why the answers are [ ] and [:#name]
class Dog2
def set_name(a_name)
#name = a_name
end
def test_instance_variables_can_be_set_by_assigning_to_them
fido = Dog2.new
assert_equal [ ], fido.instance_variables
#In this case, fido doesn't have any instance_variables,
because it is assigned to a new "Dog2" Hash/class which has none methods?
fido.set_name("Fido")
assert_equal [:#name], fido.instance_variables
#In this case, fido has an instance_variable,
because it uses the set_name methods inherited from "Dog2" classes?
end
assert_raise(SyntaxError) do
eval "fido.#name"
# NOTE: Using eval because the above line is a syntax error.
end
#So Eval here means that if the above returns "fido.#name", give it a SyntaxError?
I added some comments under those 2 cases, see if I understand it correctly.
When the first assert_equal is called, the Dog2 instance (fido) has no instance variables, because none were defined in the initializer or in any other way.
When set_name is called the #name instance variable gets set, so by the time the second assert_equal is called the fido instance does have an instance variable, one, #name, so the fido.instance_variables method returns an array with that one symbol.
Update
In response to the questions you pose in the comments in your code sample:
No, your first two comments are not accurate. It's not because it has no methods, it's because it has no instance variables. The fido instance does have a method, the set_name method.
Your second comment is not accurate because there's no inheritance going on here, fido is an instance of Dog2 and so once the set_name method has been called it has an instance variable, because #name is initialized in that set_name method.
Your final comment/question about the eval is just because if the authors had actually written just fido.#name then the code itself would have failed to run, they wanted it to run but display that if you'd written fido.#name in your code then Ruby would have exited and refused to run your code because of that syntax error.
Another Update
After another question from the OP I wanted to just add, although #name exists inside the set_name method, the point really of this example is to show that in Ruby until set_name is called that #name variable doesn't exist yet. In other languages you would define all the instance variables up front, so they'd always exist for any instantiated object of that class.
Ruby is a much more dynamic language and so until that #name = a_name line of code is actually executed, the #name variable doesn't exist, so is not returned in fido.instance_variables
This would also be true even if that method is called, but that #name = a_name line isn't executed, so, eg.
class Dog2
def set_name a_name
if false
#name = a_name
end
end
end
fido = Dog2.new
fido.set_name "Fido"
fido.instance_variables # => []
Hope that helps.