RSpec any_instance stubbing on object initialised in function - ruby

Disclaimer: I'm very new to RSpec and TDD in general, so I may be going about this the wrong way entirely.
I want to write a command-line interface to my program which takes a command and generates a class to handle it. The way the program is intended to work is similar to tools like git and svn; i.e. you could pass "srs init" to initialise the program, "srs add" to add something to it, and so forth.
So I have a class which takes ARGV and passes it off to a specific handler, which looks like this:
class CLI
def run!(*arguments)
command = arguments.shift
case command
when "init"
CLI::Init.new.run!(*arguments)
end
end
end
My Init handler would then look like this:
class CLI
class Init
def initialize()
end
def run!(*arguments)
end
end
end
I am trying to write a test suite for the CLI class's routing functionality. RSpec fails if I use the following:
describe CLI do
it "should launch the Init handler if we pass init" do
CLI::Init.any_instance.should_receive(:run!)
CLI::run!(*["init"])
end
end
However it passes if I replace the call to CLI::run! with a direct call to the Init handler's run; i.e.:-
describe CLI do
it "should launch the Init handler if we pass init" do
CLI::Init.any_instance.should_receive(:run!)
CLI::Init.new.run!(*[])
end
end
It looks as if any_instance only works on instances defined/constructed within the describe block, but I'm not really sure. If anyone can offer me any guidance either on how I can check that a class method has been called on an instance constructed inside my run! function, or on a better way to test this functionality in the first place, I'd be most appreciative.

Sometimes explaining the problem reveals the answer. In actual fact, the name of the handler, "Init" was being passed as a parameter to the describe block, more like this:
%w{Init}.each do |cmd|
describe CLI do
it "should launch the #{cmd} handler if we pass #{cmd}" do
CLI.const_get(cmd).any_instance.should_receive(:run!)
CLI::run!(*[cmd])
end
end
end
In describing the problem I took out the loop to simplify the question, but in doing so made a crucial change -- the name of the class, Init, began with a capital "I", while the name of the command passed to the command line, init, begins with a small "i".
So it turned out the test failed correctly, because I tried to pass the command "Init" when I should have been passing the command "init".
TL;DR -- the original code does actually work! Sorry for the bother.

Related

RSpec loop testing with console input and output

I have an input method, that intended to read circle radius from console. If input is invalid, method outputs error message and loops to read input again.
So I need to make an rspec test that iterates by array of invalid inputs and expect that
input method will output error to console message each time.
Here is my input class:
# frozen_string_literal: true
require_relative '../data/messages'
# Input class is responsible for reading and writing to and from console and
# querying user
class Input
def read
loop do
print "#{RADIUS_QUERY_MSG}\n> "
radius = gets.strip
return radius.to_f if valid?(radius)
puts INVALID_MSG
end
end
private
def valid?(radius)
/\A[+]?\d+(\.\d+)?\z/.match(radius)
end
end
I've tried this in my rspec test, but it seems to get into some infinite loop:
# frozen_string_literal: true
require 'input'
require_relative '../data/messages'
require_relative '../data/rspec'
RSpec.describe Input do
let(:input) { described_class.new }
describe '#read' do
INVALID_INPUTS.each do |invalid_input|
context "with invalid input \"#{invalid_input}\"" do
it 'tells user that input is invalid' do
allow(input).to receive(:gets).and_return(invalid_input)
expect(input.read).to output("#{INVALID_MSG}\n").to_stdout
end
end
end
end
end
How can I do this properly? Would appreciate any help.
P.S.
Found this article, but it was no use for me. Maybe it will help. https://haughtcodeworks.com/blog/software-development/easy-loop-testing/
P.P.S.
INVALID_MSG and RADIUS_QUERY_MSG are strings and INVALID_INPUTS is an array of strings.
Refactor to Inject Your Test Inputs into the "Real" Method
This is a common problem for code that isn't written test-first. There are a couple of ways to solve it, but the simplest option without mocking, stubbing, or otherwise invalidating your "real" code is simply to refactor the method itself. For example:
def read test_input: nil
loop do
print "#{RADIUS_QUERY_MSG}\n> "
radius = (test_input || gets).strip
return radius.to_f if valid?(radius)
puts INVALID_MSG
end
end
Now you can simply inject whatever values you want into the optional test_input keyword argument from your RSpec tests to ensure that the input is stripped properly and exhibits whatever other behavior you're testing for.
This avoids all sorts of problems you might experience by trying to write around a difficult-to-test method. Either you provide test input directly to the method, in which case the method uses that, or you don't, in which case it calls #gets just as it normally would.
Remember, the goal isn't to test core methods like #gets. Instead, you should be testing the behavior of your method or object given a particular input or state. If you make your methods testable by allowing dependency injection in your code, or refactoring your class to allow modifying instance variables in your test setup and using those rather than method arguments passed to your methods, you ensure that you are testing your real class or method rather than hacking your way around it.
There are certainly other, more complex ways to do what I did above, but they don't seem warranted for this specific example. The KISS principle definitely applies!

A better way to call methods on an instance

My question has a couple layers to it so please bear with me? I built a module that adds workflows from the Workflow gem to an instance, when you call a method on that instance. It has to be able to receive the description as a Hash or some basic data structure and then turn that into something that puts the described workflow onto the class, at run-time. So everything has to happen at run-time. It's a bit complex to explain what all the crazy requirements are for but it's still a good question, I hope. Anyways, The best I can do to be brief for a context, here, is this:
Build a class and include this module I built.
Create an instance of Your class.
Call the inject_workflow(some_workflow_description) method on the instance. It all must be dynamic.
The tricky part for me is that when I use public_send() or eval() or exec(), I still have to send some nested method calls and it seems like they use 2 different scopes, the class' and Workflow's (the gem). When someone uses the Workflow gem, they hand write these method calls in their class so it scopes everything correctly. The gem gets to have access to the class it creates methods on. The way I'm trying to do it, the user doesn't hand write the methods on the class, they get added to the class via the method shown here. So I wasn't able to get it to work using blocks because I have to do nested block calls e.g.
workflow() do # first method call
# first nested method call. can't access my scope from here
state(:state_name) do
# second nested method call. can't access my scope
event(:event_name, transitions_to: :transition_to_state)
end
end
One of the things I'm trying to do is call the Workflow#state() method n number of times, while nesting the Workflow#event(with, custom_params) 0..n times. The problem for me seems to be that I can't get the right scope when I nest the methods like that.
It works just like I'd like it to (I think...) but I'm not too sure I hit the best implementation. In fact, I think I'll probably get some strong words for what I've done. I tried using public_send() and every other thing I could find to avoid using class_eval() to no avail.
Whenever I attempted to use one of the "better" methods, I couldn't quite get the scope right and sometimes, I was invoking methods on the wrong object, altogether. So I think this is where I need the help, yeah?
This is what a few of the attempts were going for but this is more pseudo-code because I could never get this version or any like it to fly.
# Call this as soon as you can, after .new()
def inject_workflow(description)
public_send :workflow do
description[:workflow][:states].each do |state|
state.map do |name, event|
public_send name.to_sym do # nested call occurs in Workflow gem
# nested call occurs in Workflow gem
public_send :event, event[:name], transitions_to: event[:transitions_to]
end
end
end
end
end
From what I was trying, all these kinds of attempts ended up in the same result, which was my scope isn't what I need because I'm evaluating code in the Workflow gem, not in the module or user's class.
Anyways, here's my implementation. I would really appreciate it if someone could point me in the right direction!
module WorkflowFactory
# ...
def inject_workflow(description)
# Build up an array of strings that will be used to create exactly what
# you would hand-write in your class, if you wanted to use the gem.
description_string_builder = ['include Workflow', 'workflow do']
description[:workflow][:states].each do |state|
state.map do |name, state_description|
if state_description.nil? # if this is a final state...
description_string_builder << "state :#{name}"
else # because it is not a final state, add event information too.
description_string_builder.concat([
"state :#{name} do",
"event :#{state_description[:event]}, transitions_to: :#{state_description[:transitions_to]}",
"end"
])
end
end
end
description_string_builder << "end\n"
begin
# Use class_eval to run that workflow specification by
# passing it off to the workflow gem, just like you would when you use
# the gem normally. I'm pretty sure this is where everyone's head pops...
self.class.class_eval(description_string_builder.join("\n"))
define_singleton_method(:has_workflow?) { true }
rescue Exception => e
define_singleton_method(:has_workflow?) { !!(puts e.backtrace) }
end
end
end
end
# This is the class in question.
class Job
include WorkflowFactory
# ... some interesting code for your class goes here
def next!
current_state.events.#somehow choose the correct event
end
end
# and in some other place where you want your "job" to be able to use a workflow, you have something like this...
job = Job.new
job.done?
# => false
until job.done? do job.next! end
# progresses through the workflow and manages its own state awareness
I started this question off under 300000 lines of text, I swear. Thanks for hanging in there! Here's even more documentation, if you're not asleep yet.
module in my gem

How to check whether a variable is an instance of a module's subclass using rspec?

I have a class structure that looks like this:
module MyModule
class MyOuterClass
class MyInnerClass
end
end
end
I'm trying to make sure that a variable was correctly instantiated as a MyInnerClass using Rspec. printing the type of the class, it was MyModule::MyOuterClass::MyInnerClass. However, if I try to run the line
expect{#instance_of_MyInnerClass}.to be_an_instance_of(MyModule::MyOuterClass::MyInnerClass)
I get the error "You must pass an argument rather than a block to use the provided matcher." Additionally, the classes are in another location, so I can't just check
[...] be_an_instance_of(MyInnerClass)
Rspec complains that MyInnerClass is an uninitialized constant. So, I would like to ask how to verify that a variable is an instance of MyInnerClass using RSpec.
Don't Pass a Block
Rspec 3.x uses an expect method rather than a block syntax (see RSpec 3 Expectations 3.0). To get your spec to pass, and clean it up, you can use the following:
module MyModule
class MyOuterClass
class MyInnerClass
end
end
end
describe MyModule::MyOuterClass::MyInnerClass do
it "is correctly instantiated" do
expect(subject).to be_an_instance_of MyModule::MyOuterClass::MyInnerClass
end
end
Note the use of the implicit subject, passed as an argument to #expect. You can certainly pass other local or instance variables instead, but in this case subject is already defined for you as MyModule::MyOuterClass::MyInnerClass.new.
Most of us are using the preferred Rspec syntax, so it would be:
expect(#instance_of_MyInnerClass).to be_a MyInnerClass

Mock a `puts` to a file in Rspec

I have a silly "queue-class[1]" with the following method, that I want to spec out with Rspec. I am not interested in testing if writing to the file-system works (It works, my computer works) but in whether or not the correct data gets written away.
def write transaction
File.open("messages/#{#next_id}", "w") {|f| f.puts transaction }
#next_id += 1
end
The spec for testing this is:
describe TransactionQueue do
context "#write" do
it "should write positive values" do
open_file = mock File
open_file.stub(:puts)
File.any_instance.stub(:open).and_yield(open_file)
File.any_instance.should_receive(:open)
open_file.should_receive(:puts).with("+100")
#queue = TransactionQueue.new
#queue.write("+100")
end
end
end
Running this, fails, because my Mocks never receive the expected "open" and "puts" messages.
Can I mock File this way? Did I use the any_instance correctly; is my attempt to stub a "block-yield" correct?
I'd rather not use extra gems like FakeFS when it can be avoided; this is not so much about getting it to work; bu mostly about actually understanding what is going on. Hence my attempt to avoid extra gems/layers of complexity.
[1] Class is from The Cucumber Book; but these tests have littel to do with Cucumber itself. I somehow broke the code when following the book; and want to find out what, by writing unit-tests for the parts that the book does not write tests for: the helper classes.
It's not "any instance" of the File class that you expect to receive the open method; it's the File class itself:
File.stub(:open).and_yield(open_file)
File.should_receive(:open)
Furthermore, don't use both a stub and an expectation. If you want to verify that File.open is actually called:
File.should_receive(:open).and_yield(open_file)
If you merely want to stub the open method in case it gets called, but don't want to require it as behaviour of the #queue.write method:
File.stub(:open).and_yield(open_file)
(This is from memory, I haven't used RSpec for a few months.)

Unit testing command line interface

I am using clamp - the command line framework for my ruby application, and am unsure of how to initiate my clamp objects for unit testing. My clamp object looks like this
class myCommand < Clamp::Command
parameter "first", "first param"
parameter "second", "second param"
def execute
#Data
end
end
And is run via command line like so
$~> myCommand first second
At the moment, in my rspec tests im having to set the objects properties directly like so.
before(:each) do
$stdout = StringIO.new
#my_command = myCommand.new("")
#my_command.first= "first"
#my_command.second= "second"
end
This doesnt seem to be the proper way to initiate the clamp objects for testing, but am unsure of the correct way to do this. Wondered if anyone had any ideas. Thanks
So, what you're doing is:
creating a Command instance
setting attributes on it
calling #execute
That's a fine way to test a Clamp command, and is probably the best way to unit-test the logic of your #execute method.
If you wish to test parsing of command-line arguments, you could exercise the #parse method, and check attribute values, e.g.
before do
#command = MyCommand.new("my")
end
describe "#parse" do
before do
#command.parse(["FOO", "BAR"])
end
it "sets attribute values" do
#command.first.should == "FOO"
#command.second.should == "BAR"
end
end
But this starts to test Clamp itself, rather than your own code ... so I probably wouldn't bother.
If you want to test both parsing and execution together, try something like:
describe ".run" do
context "with two arguments" do
it "does something useful" do
MyCommand.run("cmd", ["ARG1", "ARG2"])
# ... test for usefulness
end
end
end
Again, though, the way you're currently testing is perfectly fine. I hope that helps.

Resources