I have a ruby file airplane.rb
with a ruby class like so -
class AirplaneSeat
attr_accessor :seat_row, :seat_column, :type, :order, :assigned_passenger
def initialize(seat_row, seat_column, type, order, assigned_passenger = 0)
#seat_row = seat_row
#seat_column = seat_column
#type = type
#order = order
#assigned_passenger = assigned_passenger
end
def get_passenger_seating
#some code
end
end # end of class
# outside the class
begin
puts "Enter the seating matrix as 2D array"
seat_matrix = JSON.parse gets.chomp
puts "Enter the number of passengers"
no_of_passengers = gets.chomp
raise "Please enter a valid passenger count" if (no_of_passengers.empty? || no_of_passengers.to_i <=0)
AirplaneSeat.get_passenger_seating(seat_matrix, no_of_passengers)
rescue Exception => e
puts "Error encountered -> #{e.message}"
end
So the ruby class has a few methods and couple of lines of code to execute outside the class, which takes input from the user and then calls the class method.
How do I go about writing test cases for this? I have the rspecs gem and spec folder setup done.
I don't really understand how to begin with the test cases.
Any pointers greatly appreciated.
Thanks!
As a simple example say we have our file for a Foo class, foo.rb:
class Foo
def call
'bar'
end
end
We can create a spec, foo_spec.rb:
require 'rspec'
require_relative 'foo'
RSpec.describe Foo do
describe '#call' do
it 'works' do
expect(described_class.new.call).to eq 'Bar'
end
end
end
And then from the command line we can run the spec:
$ rspec foo_spec.rb
Related
I am building a DSL and have this module
module EDAApiBuilder
module Client
attr_accessor :api_client, :endpoint, :url
def api_client(api_name)
#apis ||= {}
raise ArgumentError.new('API name already exists.') if #apis.has_key?(api_name)
#api_client = api_name
#apis[#api_client] = {}
yield(self) if block_given?
end
def fetch_client(api_name)
#apis[api_name]
end
def endpoint(endpoint_name)
raise ArgumentError.new("Endpoint #{endpoint_name} already exists for #{#api_client} API client.") if fetch_client(#api_client).has_key?(endpoint_name)
#endpoint = endpoint_name
#apis[#api_client][#endpoint] = {}
yield(self) if block_given?
end
def url=(endpoint_url)
fetch_client(#api_client)[#endpoint]['url'] = endpoint_url
end
end
end
so that I have tests like
context 'errors' do
it 'raises an ArgumentError when trying to create an already existent API client' do
expect {
obj = MixinTester.new
obj.api_client('google')
obj.api_client('google')
}.to raise_error(ArgumentError,'API name already exists.')
end
it 'raises an ArgumentError when trying to create a repeated endpoint for the same API client' do
expect {
obj = MixinTester.new
obj.api_client('google') do |apic|
apic.endpoint('test1')
apic.endpoint('test1')
end
}.to raise_error(ArgumentError,"Endpoint test1 already exists for google API client.")
end
end
I would rather have #api_clientwritten as an assignment block
def api_client=(api_name)
so that I could write
obj = MixinTester.new
obj.api_client = 'google' do |apic| # <=== Notice the difference here
apic.endpoint('test1')
apic.endpoint('test1')
end
because I think this notation (with assignment) is more meaningful. But then, when I run my tests this way I just get an error saying that the keyworkd_do is unexpected in this case.
It seems to me that the definition of an assignment block is syntactic sugar which won't contemplate blocks.
Is this correct? Does anyone have some information about this?
By the way: MixinTester is just a class for testing, defined in my spec/spec_helper.rb as
class MixinTester
include EDAApiBuilder::Client
end
SyntaxError
It seems to me that the definition of an assignment [method] is syntactic
sugar which won't contemplate blocks.
It seems you're right. It looks like no method with = can accept a block, even with the normal method call and no syntactic sugar :
class MixinTester
def name=(name,&block)
end
def set_name(name, &block)
end
end
obj = MixinTester.new
obj.set_name('test') do |x|
puts x
end
obj.name=('test') do |x| # <- syntax error, unexpected keyword_do, expecting end-of-input
puts x
end
Alternative
Hash parameter
An alternative could be written with a Hash :
class MixinTester
def api(params, &block)
block.call(params)
end
end
obj = MixinTester.new
obj.api client: 'google' do |apic|
puts apic
end
#=> {:client=>"google"}
You could adjust the method name and hash parameters to taste.
Parameter with block
If the block belongs to the method parameter, and not the setter method, the syntax is accepted :
def google(&block)
puts "Instantiate Google API"
block.call("custom apic object")
end
class MixinTester
attr_writer :api_client
end
obj = MixinTester.new
obj.api_client = google do |apic|
puts apic
end
# =>
# Instantiate Google API
# custom apic object
It looks weird, but it's pretty close to what you wanted to achieve.
I'm writing RSpec unit tests for a CommandLineInterface class that I've created for my Directory object. The CommandLineInterface class uses this Directory object to print out a list of people in my Directory. Directory has a #sort_by(param) method that returns an array of strings. The order of the strings depends on the param passed to the #sort_by method (e.g., sort_by("gender"). What would be the correct way to mock out this Directory behavior in my CLI specs? Would I use an instance_double? I am not sure how to do this for a method that takes parameters, like sorting by gender.
I'm only using Ruby and RSpec. No Rails, ActiveRecord, etc. being used here.
Snippets from the class and method I want to mock out:
class Directory
def initialize(params)
#
end
def sort_by(param)
case param
when "gender" then #people.sort_by(&:gender)
when "name" then #people.sort_by(&:name)
else raise ArgumentError
end
end
end
It all depends on how your objects are collaborating.
Some information is lacking in your question:
How does CommandLineInterface use Directory? Does it create an instance by itself or does it receive one as an argument?
Are you testing class methods or instance methods? (Prefer instance methods)
Here's how you could do it if you pass in the dependent object:
require 'rspec/autorun'
class A
def initialize(b)
#b = b
end
def foo(thing)
#b.bar(thing)
end
end
RSpec.describe A do
describe '#foo' do
context 'when given qux' do
let(:b) { double('an instance of B') }
let(:a) { A.new(b) }
it 'calls b.bar with qux' do
expect(b).to receive(:bar).with('qux')
a.foo('qux')
end
end
end
end
If the class initializes the dependant object and it isn't important to know which instance got the message you can do this:
require 'rspec/autorun'
B = Class.new
class A
def initialize
#b = B.new
end
def foo(thing)
#b.bar(thing)
end
end
RSpec.describe A do
describe '#foo' do
context 'when given qux' do
let(:a) { A.new }
it 'calls b.bar with qux' do
expect_any_instance_of(B).to receive(:bar).with('qux')
a.foo('qux')
end
end
end
end
If you just want to stub out the return value and not test whether the exact message was received, you can use allow:
require 'rspec/autorun'
B = Class.new
class A
def initialize
#b = B.new
end
def foo(thing)
thing + #b.bar(thing)
end
end
RSpec.describe A do
describe '#foo' do
context 'when given qux' do
let(:a) { A.new }
it 'returns qux and b.bar' do
allow_any_instance_of(B).to receive(:bar).with('qux') { 'jabber' }
expect(a.foo('qux')).to eq('quxjabber')
end
end
end
end
I'm writing a Mastermind game in Ruby and in the constructor of the class 'Game' I want to use gets.chomp to ask the user for its name. Pretty easy, but where I run into trouble is when testing this class in RSpec, but I can't seem to properly stub out 'gets' and 'puts', because they are in the constructor not a regular method.
class Game
def initialize
puts "Please enter your name:"
#player = Player.new(gets.chomp)
end
end
describe Game do
Game.stub(:gets).and_return("Create Code AI")
Game.stub(:puts)
subject(:game) { Game.new }
describe "#new" do
its("player.name") { eql("Create Code AI") }
end
end
class Player
attr_reader :name
def initialize(name)
#name = name
end
end
I've also tried putting the stubs into 'before' and 'let' blocks amongst other things, but nothing seems to work. Any help is appreciated!
I have a method which captures stdin and stdout to help with the testing in cases like these:
require 'stringio'
module Kernel
def capture_stdout(console_input = '')
$stdin = StringIO.new(console_input)
out = StringIO.new
$stdout = out
yield
return out.string.strip
ensure
$stdout = STDOUT
$stdin = STDIN
end
end
Now, let's assume I want to test a method that interacts with stdin/stdout:
def greet
name = gets
puts "Welcome, #{name}!"
end
I would write the following test:
require 'rspec/autorun'
RSpec.describe '#say_name' do
it 'prints name correctly' do
input = 'Joe'
result = capture_stdout(input) do
greet
end
expect(result).to eql 'Welcome, Joe!'
end
end
I presented the example above to illustrate how to test both console input and output.
In your case, the test could look like this:
describe Game do
subject(:game) do
capture_stdout('Create Code AI') { return Game.new }
end
describe "#new" do
its("player.name") { eql("Create Code AI") }
end
end
Note: In order for this to work, #player should be an accessible member of Game. So, you may want to add this to your Game class:
attr_reader :player
I have next scenario:
module Module
class CommandPattern
def initialize(value)
command = []
#var = value['something']
#abc = value['abc']
#command << value
end
def add(value)
#command << value
end
def get_command
#command
end
end
end
module Module
class Implementator
def initialize(value)
#value = value
end
def method_to_test(argument)
var = "command1"
cmd = CommandPattern.new(var)
var2 = "command2"
cmd.add(var2)
var3 = argument
cmd.add(var3)
commands = var + var2 + var3
commands
end
end
end
So, when I'm testing Module::B.method_I_want_to_test, what would be the best practice to mock "var = A.new(some_stuff)"? Beside refactoring and moving this line into separate method, is there some nice way to do this?
Little bit of background on this question - this style (Module::ClassA and Module::ClassB) - I'm using http://naildrivin5.com/gli/ and reason for this approach is that class A is actually implementing Command Pattern.
So issue I was apparently getting was due to wrong way of trying to write specs.
What I did before was (on the way how #spickermann advised):
RSpec.describe Module::Implementator do
describe "#method_to_test" do
let(:command_argument) { "command" }
let(:cmnd) { double(CommandPattern, :new => command_argument, :add => command_argument)}
subject(:method_to_test) do
Implementator.new("value").method_to_test("dejan")
end
before do
allow(CommandPattern).to receive(:new).with(any_args).and_return(cmnd)
allow(CommandPattern).to receive(:add).with(any_args).and_return(cmnd)
end
it 'does something' do
expect{ method_to_test }.not_to raise_error
end
it 'does something else' do
result = method_to_test
expect(result).to eq("command1command2dejan")
end
end
end
Issue was apparently in testing Module::Implementator, didn't realise I can put module around my RSpec.describe block and solve my first issue:
module Module
RSpec.describe Implementator do
describe "#method_to_test" do
let(:command_argument) { "command" }
let(:cmnd) { double(CommandPattern, :new => command_argument, :add => command_argument)}
subject(:method_to_test) do
Implementator.new("value").method_to_test("dejan")
end
before do
allow(CommandPattern).to receive(:new).with(any_args).and_return(cmnd)
allow(CommandPattern).to receive(:add).with(any_args).and_return(cmnd)
end
it 'does something' do
expect{ method_to_test }.not_to raise_error
end
it 'does something else' do
result = method_to_test
expect(result).to eq("command1command2dejan")
end
end
end
end
Another issue I had was global variable keeping YAML structure, which I missed to see and declare in spec_helper.rb
However, thank's to #spickermann's advices, issue is solved.
I would start with something like this:
describe '#method_I_want_to_test' do
let(:something) { # whatever something needs to be }
let(:a) { double(A, # methods you need from a) }
subject(:method_I_want_to_test) do
B.new(something).method_I_want_to_test
end
before do
allow(A).to receive(:new).with(something).and_return(a)
end
it 'does what I expect' do
expect(method_I_want_to_test).to eq(# what do_semething_else returns)
end
end
The interesting part is the before block that stubs the new method on A. It returns always the double defined in the let(:a) line instead of a real instance of A
Say I make a class with a method in it.
class A
def test
puts 'test'
end
end
I want to know what goes on inside of test. I want to literally output:
def test
puts 'test'
end
Is there any way to output the source of a method in a string?
You can use Pry to view methods
# myfile.rb
require 'pry'
class A
def test
return 'test'
end
end
puts Pry::Method(A.new.method(:test)).source #(1)
# or as suggested in the comments
puts Pry::Method.from_str("A#test").source #(2)
# uses less cpu cycles than #(1) because it does not call initialize - see comments
puts Pry::Method(A.allocate.method(:test)).source #(3)
# does not use memory to allocate class as #(1) and #(3) do
puts Pry::Method(A.instance_method(:test)).source #(4)
Then run ruby myfile.rb and you will see:
def test
return 'test'
end