How to write rspec for logic and write mock STDIN - ruby

I am a very new coder and trying to write rspec for a class that test the conditional statement/logic. I started sudo coding for it but I was told to make mock STDIN which I don't know how to. Can someone please write the rspec for the class or give me a few idea how to create a mock STDIN. I need help writing rspec for the conditional statement/logic, if some can please just write the test for one of the context then I can do rest based on that.
require 'rails_helper'
module BAB::ACA
RSpec.describe partfinder do
describe '#find_part_id' do
let(:face) { create(:face) }
subject { described_class.find_part_id(face) }
context 'When bab con already exists' do
context 'when there are more than one part ids' do
#create part ids
context 'when user input matches an existing id' do
#mock STDIN that matches an existing, subject should equal that id
end
context 'when user input does not match an existing id' do
# mock STDIN that does match existing id, should return failure message
end
end
context 'when there is only one bab part id' do
# subject should equal the one that already exists
end
end
context 'when av con does not yet exist' do
# mock STDIN and make sure subject equals what you mocked
end
end
end
module BAB::ACA
class partfinder
def self.find_part_id(face)
av_con = BAB::Child:Fail.find_by(
face: face
reg: BAB:Child.find_reg
)
if av_con
look_id(face, av_con)
end
else
puts "What is #{face.name} BAB part id? must be 6"
STDIN.gets.chomp
end
end
def self.look_id(face, av_con)
if av_con.part_ids.length > 1
ask_for_id(face, av_con)
else
av.con.part_ids.first
end
end
def self.ask_for_id(face, av_con)
puts "What is #{face.name} BAB part id? "
bab_part_id = STDIN.gets.chomp
unless av.con.part_ids.include?(bab_part_id)
fail 'Entered id doesn't match'
end
bab_part_id
end
end
end

You can use method stubs.
In this case you want to stub STDIN.gets.chomp, so you'd do something like this:
describe '#find_part_id' do
before do
allow(STDIN.gets).to receive(:chomp).and_return(stdin_input)
end
let(:stdin_input) { 'user input from stdin' }
let(:face) { create(:face) }
subject { described_class.find_part_id(face) }
context 'When bab con already exists' do
context 'when there are more than one part ids' do
it 'some test' do
# your test here
end
end
# more contexts...
context 'a context that needs a different stdin_input' do
let(:stdin_input) { 'some different user input from stdin' }
it 'another test' do
# your test here
end
end
end
end
Where stdin_input is the string you want the user to enter for your tests.

Related

How to test if a DelayedJob is calling a method from another class?

I'm setting up specs to test whether or not a job calls a method under certain conditions.
This is what I have so far:
describe RandomJob do
context "when payload[:type] = MyModel" do
let!(:my_model) { create :my_model }
let!(:payload) { { type: "MyModel", id: my_model.id } }
context "when Model exists" do
it "calls MyModel.fire! with payload" do
RandomJob.perform_now(payload)
expect_any_instance_of(MyModel).to receive(:fire!).with(payload)
end
end
context "when Model does not exist" do
it "does not call MyModel.fire!" do
RandomJob.perform_now(payload)
expect_any_instance_of(MyModel).not_to receive(:fire!)
end
end
end
end
Just to be sure my way of testing worked. I setup my job like this:
class RandomJob < ApplicationJob
def perform(payload)
#payload = payload
fire_model!
end
private
def fire_model!
my_model&.fire! #payload
end
def my_model
MyModel.find(#payload[:id])
end
end
I expected the first test to pass, and the second to fail. However, my first test is failing while the second is passing.
What am I doing wrong?
You have to put the expectation before the perform_now call.
context "when Model exists" do
it "calls MyModel.fire! with payload" do
expect_any_instance_of(MyModel).to receive(:fire!).with(payload)
RandomJob.perform_now(payload)
end
end
context "when Model does not exist" do
it "does not call MyModel.fire!" do
expect_any_instance_of(MyModel).not_to receive(:fire!)
RandomJob.perform_now(payload)
end
end

rspec 3 error when user is prompted for input

I have code that is requesting the user for input, such as:
class Foo
def prompt_for_foobar
puts "where is the foobar?"
gets.chomp
end
end
I would like to test that my application is asking "where is the foobar?". My test will pass upon commenting out 'gets.chomp'. but that is needed and the anything else I have tried has given me a Errno::ENOENT: error.
it "should prompt user" do
console = Foo.new
request = "where is the foobar?"
expect { console.prompt_for_foobar }.to output(request).to_stdout
end
What is the best way to test this method?
Not sure if this is the best way to handle this, but you can send puts and gets to STDOUT and STDIN.
class Foo
def prompt_for_foobar
STDOUT.puts "where is the foobar?"
STDIN.gets.chomp
end
end
Then, test that STDIN receives that puts message with your desired object.
describe Foo do
let(:foo) { Foo.new }
before(:each) do
allow(STDIN).to receive(:gets) { "user input" }
end
describe "#prompt_for_foobar" do
it "prompts the user" do
expect(STDOUT).to receive(:puts).with("where is the foobar?")
foo.prompt_for_foobar
end
it "returns input from the user" do
allow(STDOUT).to receive(:puts)
expect(foo.prompt_for_foobar).to eq "user input"
end
end
end
The problem is that gets is a method that forces human interaction (at least in the context of RSpec, where stdin isn't connected to a pipe from another process), but the entire point of automated testing tools like RSpec are to be able to run tests without involving human interaction.
So, rather than relying directly on gets in your method, I recommend you rely on a collaborator object that implements a particular interface -- that way in the test environment you can provide an implementation of that interface that provides a response without human interaction, and in other environments it can use gets to provide a response. The simplest collaborator interface here is probably a proc (they're perfect for this kind of thing!), so you could do the following:
class Foo
def prompt_for_foobar(&responder)
responder ||= lambda { gets }
puts "where is the foobar?"
responder.call.chomp
end
end
RSpec.describe Foo do
it 'prompts the user to respond' do
expect { Foo.new.prompt_for_foobar { "" } }.to output(/where is the foobar/).to_stdout
end
it "returns the responder's response" do
expect(Foo.new.prompt_for_foobar { "response" }).to eq("response")
end
end
Notice that prompt_for_foobar no longer calls gets directly; instead it delegates the responsibility of getting a response to a responder collaborator. By default, if no responder is provided, it uses gets as a default implementation of a responder. In your test you can easily provide a responder that requires no human interaction simply by passing a block that returns a string.

Testing with Rspec - The correct way

My weakest point when it comes to coding, is using TDD & BDD methods - I tend to just write code.. but it is something that I am trying to work on.
Could anyone point out the best way to go about the following problem:
Class1:
module TempMod
class MyClass
def initalize(config)
#config = config
end
def process(xml)
if react_upon? xml.something
puts 'yeah'
else
puts 'nah'
end
end
def react_upon?(xml_code)
#code here
end
end
end
So lets say I wanted to test this class, or build it from a TDD point of view so I write my tests:
describe TempMod::MyClass do
let(:config) {double}
let(:myclass) {TempMod::MyClass.new config}
context 'Given that the xml is something we react upon' do
it 'should check that it is valid' do
myclass.process '<some><xml>here</xml></some>'
end
it 'should output yea'
end
end
How do I test that it is calling the react_upon? method. Do I even want to see it is calling it?
Is the proper way to test it, to test all the functions like the react_upon? itself independently of the other functions?
This is properly the main thing that is most confusing me with this sort of testing. Am I testing the whole class, or just individually testing the functions, and not their interactions with the other functions in that class?
Also I realize the the react_upon? might not adhere to the Single responsibility principle and I would probably move that out to its own module/class which I could test using a stub.
If anyone can shed some light on this for me that would be awesome.
edit:
describe TempMod::MyClass do
let (:valid_planning_status_xml) {
'<StatusUpdate> <TitleId>2329</TitleId> <FromStatus>Proposed</FromStatus> <ToStatus>Confirmed</ToStatus> </StatusUpdate>'
}
let(:config) { double }
let(:status_resolver) { double }
subject(:message_processor) { TempMod::MyClass.new config, status_resolver }
context 'Given that the message XML is valid' do
it 'should check the context of the message' do
expect(message_processor.process valid_planning_status_xml).to call :check_me
end
context 'Given that the message is for a planning event update' do
it 'should call something' do
pending
end
end
context 'Given that the message is for a recording job update' do
end
context 'Given that the message is for a video title update' do
end
end
end
Your question confused me a bit is this what you are asking
module TempMod
class MyClass
def initalize(config)
#config = config
end
def process(xml)
react_upon?(xml.something) ? 'yeah' : 'nah'
end
def react_upon?(xml_code)
#code here
end
end
end
Then test like
describe TempMod::MyClass do
let(:config) {double}
let(:myclass) {TempMod::MyClass.new config}
context 'Given that the xml is something we react upon' do
it "should respond to react_upon?" do
expect(myclass).to respond_to(:react_upon?)
end
it "should react_upon? valid xml" do
expect(myclass.react_upon?(YOUR VALID REACTION GOES HERE)).to be_true
end
it "should not react_upon? invalid xml" do
expect(myclass.react_upon?(YOUR INVALID REACTION GOES HERE)).to be_false
end
it "should say 'yeah' if it is valid" do
expect(myclass.process('<some><xml>here</xml></some>')).to eq('yeah')
end
it "should say 'nah' if it is invalid" do
expect(myclass.process('<some><xml>here</some>')).to eq('nah')
end
it 'should check the context of the message' do
expect(myclass).to receive(:react_upon?).with('<some><xml>here</xml></some>')
myclass.process('<some><xml>here</xml></some>')
end
end
end
Right now your tests have no expectations so I added one that expects myclass to respiond_to the react_upon? method and another that expects myclass.process(xml) to respond with a String that equals yeah.

Passing an object as subject to rspec

I am running rspec tests on a catalog object from within a Ruby app, using Rspec::Core::Runner::run:
File.open('/tmp/catalog', 'w') do |out|
YAML.dump(catalog, out)
end
...
unless RSpec::Core::Runner::run(spec_dirs, $stderr, out) == 0
raise Puppet::Error, "Unit tests failed:\n#{out.string}"
end
(The full code can be found at https://github.com/camptocamp/puppet-spec/blob/master/lib/puppet/indirector/catalog/rest_spec.rb)
In order to pass the object I want to test, I dump it as YAML to a file (currently /tmp/catalog) and load it as subject in my tests:
describe 'notrun' do
subject { YAML.load_file('/tmp/catalog') }
it { should contain_package('ppet') }
end
Is there a way I could pass the catalog object as subject to my tests without dumping it to a file?
I am not very clear as to what exactly you are trying to achieve but from my understanding I feel that using a before(:each) hook might be of use to you. You can define variables in this block that are available to all the stories in that scope.
Here is an example:
require "rspec/expectations"
class Thing
def widgets
#widgets ||= []
end
end
describe Thing do
before(:each) do
#thing = Thing.new
end
describe "initialized in before(:each)" do
it "has 0 widgets" do
# #thing is available here
#thing.should have(0).widgets
end
it "can get accept new widgets" do
#thing.widgets << Object.new
end
it "does not share state across examples" do
#thing.should have(0).widgets
end
end
end
You can find more details at:
https://www.relishapp.com/rspec/rspec-core/v/2-2/docs/hooks/before-and-after-hooks#define-before(:each)-block

Correct way to TDD methods that calls other methods

I need some help with some TDD concepts. Say I have the following code
def execute(command)
case command
when "c"
create_new_character
when "i"
display_inventory
end
end
def create_new_character
# do stuff to create new character
end
def display_inventory
# do stuff to display inventory
end
Now I'm not sure what to write my unit tests for. If I write unit tests for the execute method doesn't that pretty much cover my tests for create_new_character and display_inventory? Or am I testing the wrong stuff at that point? Should my test for the execute method only test that execution is passed off to the correct methods and stop there? Then should I write more unit tests that specifically test create_new_character and display_inventory?
I'm presuming since you mention TDD the code in question does not actually exist. If it does then you aren't doing true TDD but TAD (Test-After Development), which naturally leads to questions such as this. In TDD we start with the test. It appears that you are building some type of menu or command system, so I'll use that as an example.
describe GameMenu do
it "Allows you to navigate to character creation" do
# Assuming character creation would require capturing additional
# information it violates SRP (Single Responsibility Principle)
# and belongs in a separate class so we'll mock it out.
character_creation = mock("character creation")
character_creation.should_receive(:execute)
# Using constructor injection to tell the code about the mock
menu = GameMenu.new(character_creation)
menu.execute("c")
end
end
This test would lead to some code similar to the following (remember, just enough code to make the test pass, no more)
class GameMenu
def initialize(character_creation_command)
#character_creation_command = character_creation_command
end
def execute(command)
#character_creation_command.execute
end
end
Now we'll add the next test.
it "Allows you to display character inventory" do
inventory_command = mock("inventory")
inventory_command.should_receive(:execute)
menu = GameMenu.new(nil, inventory_command)
menu.execute("i")
end
Running this test will lead us to an implementation such as:
class GameMenu
def initialize(character_creation_command, inventory_command)
#inventory_command = inventory_command
end
def execute(command)
if command == "i"
#inventory_command.execute
else
#character_creation_command.execute
end
end
end
This implementation leads us to a question about our code. What should our code do when an invalid command is entered? Once we decide the answer to that question we could implement another test.
it "Raises an error when an invalid command is entered" do
menu = GameMenu.new(nil, nil)
lambda { menu.execute("invalid command") }.should raise_error(ArgumentError)
end
That drives out a quick change to the execute method
def execute(command)
unless ["c", "i"].include? command
raise ArgumentError("Invalid command '#{command}'")
end
if command == "i"
#inventory_command.execute
else
#character_creation_command.execute
end
end
Now that we have passing tests we can use the Extract Method refactoring to extract the validation of the command into an Intent Revealing Method.
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
if command == "i"
#inventory_command.execute
else
#character_creation_command.execute
end
end
def invalid?(command)
!["c", "i"].include? command
end
Now we finally got to the point we can address your question. Since the invalid? method was driven out by refactoring existing code under test then there is no need to write a unit test for it, it's already covered and does not stand on it's own. Since the inventory and character commands are not tested by our existing test, they will need to be test driven independently.
Note that our code could be better still so, while the tests are passing, lets clean it up a bit more. The conditional statements are an indicator that we are violating the OCP (Open-Closed Principle) we can use the Replace Conditional With Polymorphism refactoring to remove the conditional logic.
# Refactored to comply to the OCP.
class GameMenu
def initialize(character_creation_command, inventory_command)
#commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
#commands[command].execute
end
def invalid?(command)
!#commands.has_key? command
end
end
Now we've refactored the class such that an additional command simply requires us to add an additional entry to the commands hash rather than changing our conditional logic as well as the invalid? method.
All the tests should still pass and we have almost completed our work. Once we test drive the individual commands you can go back to the initialize method and add some defaults for the commands like so:
def initialize(character_creation_command = CharacterCreation.new,
inventory_command = Inventory.new)
#commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
The final test is:
describe GameMenu do
it "Allows you to navigate to character creation" do
character_creation = mock("character creation")
character_creation.should_receive(:execute)
menu = GameMenu.new(character_creation)
menu.execute("c")
end
it "Allows you to display character inventory" do
inventory_command = mock("inventory")
inventory_command.should_receive(:execute)
menu = GameMenu.new(nil, inventory_command)
menu.execute("i")
end
it "Raises an error when an invalid command is entered" do
menu = GameMenu.new(nil, nil)
lambda { menu.execute("invalid command") }.should raise_error(ArgumentError)
end
end
And the final GameMenu looks like:
class GameMenu
def initialize(character_creation_command = CharacterCreation.new,
inventory_command = Inventory.new)
#commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
#commands[command].execute
end
def invalid?(command)
!#commands.has_key? command
end
end
Hope that helps!
Brandon
Consider refactoring so that the code that has responsibility for parsing commands (execute in your case) is independent of the code that implements the actions (i.e., create_new_character, display_inventory). That makes it easy to mock the actions out and test the command parsing independently. You want independent testing of the different pieces.
I would create normal tests for create_new_character and display_inventory, and finally to test execute, being just a wrapper function, set expectations to check that the apropriate command is called (and the result returned). Something like that:
def test_execute
commands = {
"c" => :create_new_character,
"i" => :display_inventory,
}
commands.each do |string, method|
instance.expects(method).with().returns(:mock_return)
assert_equal :mock_return, instance.execute(string)
end
end

Resources