Unit testing command line interface - ruby

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.

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!

Rspec - How to write specs for a chain of methods

I'm learning rspec, and I'm wondering what the most effective way to write specs for a method that calls a chain of other methods. For example:
class Example1
def foo(dependency)
dependency.bar("A")
dependency.baz("B")
dependency.bzz("C")
end
end
Ideally I would like to write specs like this:
it "should call bar" do
ex = Example1.new
dep = mock
dep.should_receive(:bar).with("A")
ex.foo(dep)
end
it "should call baz"
...
it "should call bzz"
...
When I do that, however, I (understandably) get exceptions like 'unexpected method call baz'.
So what's the best way to deal with that? I have come up with a couple of ideas but I don't know if any of them are good.
Make the mock dependency an "as_null_object" so it ignores the extra calls. (Down side - if I was calling unwanted random stuff on that object, I wouldn't know it)
Stub out the two unused dependency method calls in each spec (Down side - feels very DRY)
Stub out all three dependency calls in a 'before' (Down side - puts a lot of junk in the 'before')
It sounds like you have already worked out which options RSpec gives you. I would go with option 1 and use as_null_object. It's true that you might be missing other random method calls on that object but I would be ok with that if the point of each of these tests was simply to assert that a particular method was being called, especially if I have higher level integration tests covering this method.
If you really need to verify that no other methods are called on dependency then option 3 may make sense but such tests can be brittle when implementation changes.
As an aside, to make your test a little simpler you can use subject to avoid explicitly instantiating Example1 (assuming you are using a describe Example1 block), e.g.:
subject.foo(dep)
(However see discussion in comments - an implicit subject can hide intention).
RSpec has a feature called stub_chain: https://www.relishapp.com/rspec/rspec-mocks/v/2-0/docs/stubs/stub-a-chain-of-methods
What about testing them all in one example?
it "should call bar"
ex = Example1.new
dep = mock
dep.should_receive("bar").with("A")
dep.should_receive("baz").with("B")
dep.should_receive("bzz").with("C")
ex.foo(dep)
end
I believe you can use RSpec to verify the order in which they are called, if that matters.
However, this kind of approach often indicate that there is a problem with how the code is written, e.g. a Law Of Demeter violation. In your example, foo should be a methed on the dependency's class.
I would test this code in this way:
describe "checking foo method" do
before(:each) do
#ex = Example1.new
#test = ClassOfDependency.any_instance
#test.as_null_object
end
after(:each) do
#ex.foo(dependency)
end
it "should call bar method" do
#test.should_receive(:bar).with("A")
end
it "should call baz method" do
#test.should_receive(:baz).with("B")
end
it "should call bzz method" do
#test.should_receive(:bzz).with("C")
end
end
But I'm not sure that it will work, hope it'll give you some ideas.

RSpec any_instance stubbing on object initialised in function

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.

How to mock an TCP connecting in Cucumber

I want to test one program which can capture and send IP packets to some clients, so how to mock request or client in Cucumber? thanks
Normally I would answer the question with the cavet that it's a bad idea but this is such a bad idea I'm only going to answer half of it, how to mock in Cucumber generically.
You see Cucumber is meant to be a total test from the outside in so it's meant to completely run your code without any test doubles. The whole point is you are not unit testing but are testing your whole application.
"We recommend you exercise your whole stack when using Cucumber. [However] you can set up mocks with expectations in your Step Definitions." - Aslak Hellesøy, Creator of Cucumber
Granted you can do this but you are going to need to write your own the TCPServer and TCPSocket classes to avoid using the network and that can actually introduce bugs since your writing specs against your mock Net classes not the actual Net classes. Again, not a good idea.
Enough yapping, here's how to use mocks in Cucumber. (I'm going to assume you have a basic understanding of Cucumber and Ruby so I will skip some steps like how to require your class files in Cucumber.)
Let's say you have the following classes:
class Bar
def expensive_method
"expensive method called"
end
end
class Foo
# Note that if we don't send a bar it will default to the standard Bar class
# This is a standard pattern to allow test injection into your code.
def initialize(bar=Bar.new)
#bar = bar
puts "Foo.bar: #{#bar.inspect}"
end
def do_something
puts "Foo is doing something to bar"
#bar.expensive_method
end
end
You should have the Bar and Foo classes required in your features/support/env.rb file but to enable RSpec mocks you need to add the following line:
require 'cucumber/rspec/doubles'
Now create a feature file like this one:
Feature: Do something
In order to get some value
As a stake holder
I want something done
Scenario: Do something
Given I am using Foo
When I do something
Then I should have an outcome
And add the steps to your step definitions file:
Given /^I am using Foo$/ do
# create a mock bar to avoid the expensive call
bar = double('bar')
bar.stub(:expensive_method).and_return('inexpensive mock method called')
#foo = Foo.new(bar)
end
When /^I do something$/ do
#outcome = #foo.do_something
# Debug display of the outcome
puts ""
puts "*" * 40
puts "\nMocked object call:"
puts #outcome
puts ""
puts "*" * 40
end
Then /^I should have an outcome$/ do
#outcome.should_not == nil
end
Now when you run your feature file you should see:
****************************************
Mocked object call:
inexpensive mock method called
****************************************

Is there a way to undo Mocha stubbing of any_instance in Test::Unit

Much like this question, I too am using Ryan Bates's nifty_scaffold. It has the desirable aspect of using Mocha's any_instance method to force an "invalid" state in model objects buried behind the controller.
Unlike the question I linked to, I'm not using RSpec, but Test::Unit. That means that the two RSpec-centric solutions there won't work for me.
Is there a general (ie: works with Test::Unit) way to remove the any_instance stubbing? I believe that it's causing a bug in my tests, and I'd like to verify that.
As it happens, Mocha 0.10.0 allows unstubbing on any_instance().
str = "Not Stubbed!"
String.any_instance.stubs(:to_s).returns("Stubbed!")
puts str.to_s # "Stubbed!"
String.any_instance.unstub(:to_s)
puts str.to_s # "Not Stubbed!"
Mocha does not provide such a functionality. However you can implement it yourself.
The first thing we should know about mocha is that mocha actually replaces the original methods when you stub them. So in order to be able to restore these methods later, you must keep a reference to the former ones. It can be easily achieved by: alias new_method old_method.
It must be done before mocking the old_method.
Now, to unmock a method, you only need to alias old_method new_method.
Consider the following code:
class A
def a
true
end
end
class TestA < Test::Unit::TestCase
def test_undo_mock
a = A.new
A.class_eval {alias unmocked_a a}
A.any_instance.stubs(:a).returns("b")
assert a.a, "b"
A.class_eval {alias a unmocked_a}
assert a.a, "a"
end
end
If you want to remove all your stubs/expectations in one go, then you can do that using mocha_teardown (eg. call self.mocha_teardown).
May be a little bit destructive in this case, however.

Resources