I built a Thor script that connects to an HTTP API to perform some very simple actions. I've coded tests for the backend but the Thor script is basically untested, which is quite suboptimal.
My first approach was to capture the output of the commands itself and write test against such output, the resulting tests are unsurprisingly slow.
expect(`bin/script foo`).to eq('bar')
I then tried to use both webmock and vcr but using this approach none of these frameworks are invoked, even if I mock the exact request the mock is unused, most probably because both webmock and vcr are unable to hook into the thor script.
Has anybody found a nice solution for this? Invoking the Thor script directly (Thorclass.action('bar')) would be enough for my taste, but I haven't found a way to do it.
Any suggestion? Thanks in advance.
Thor is a wrapper
I tend to see Rake, Thor and friends as another interface to your code
I keep my Thor/Rake code as tiny as possible
All production code is kept in a standard Ruby class
That means unit testing via VCR becomes dead easy
Also allows you to reuse your production code in another interface: e.g. a Rails controller
Example
Thor wrapper
bin/seed
#!/usr/bin/env ruby
require "thor"
class Seed < Thor
desc "budgets", "Seeds budgets"
def budgets
puts 'Seeding currencies...'
SeedBudgets.new.call
puts 'Done.'
end
end
Seed.start
For more details on command line Thor see this excellent walkthrough
Production code
lib/services/seed_budgets.rb
class SeedBudgets
def initialize
# I find an initialize helpful for injecting dependencies
end
def call
# Code goes here
end
end
Unit tests
test/services/seed_budgets_test.rb
require 'minitest/autorun'
require 'vcr'
VCR.configure do |config|
config.cassette_library_dir = 'fixtures/vcr_cassettes'
config.hook_into :webmock
end
class SeedBudgetsTest < Minitest::Test
def test_seeds_one_budget
VCR.use_cassette('one_budget_from_api') do
SeedBudgets.new.call
assert_equal 1, Budget.count
end
end
end
That will allow you to decouple the command line interface from the actual code.
Then Thor becomes a very thin wrapper around your actual code.
Feel free to post more detailed code and I can help more. :)
Related
I'm building a gem that allows for a much more convenient and configurable 'console' for ruby gem development than the current options (ie: 'bundle console').
As such, one of if not the most important aspects of the entire gem is that it does in fact open a console session, which I currently have set up in a start method:
class MyConsole
def start
Pry.start(self)
end
end
I am trying to test this functionality, but it's difficult because their's not a lot of good resources for this. It's also really annoying because every time I run this method in rspec, pry opens, and I have to exit it before finishing the rest of the tests.
I have three main questions:
How can I run a method in rspec that starts pry without actually starting pry?
How can I specifically test that that method does in fact start pry?
Assuming 1 and 2 are possible, how can I test what context the method starts pry in? (notice how the start method calls Pry.start(self), meaning the pry session should open in the context of a MyConsole instance.)
The best way to do this would probably be using an RSpec spy.
Your test would probably look something like this:
describe MyConsole do
describe '#start' do
it 'calls Pry.start' do
described_class.start
expect(Pry).to have_received(:start).with('your_args_here')
end
end
end
This is a really good explanation of RSpec stubbing options, IMO, where you can learn more: https://about.futurelearn.com/blog/stubs-mocks-spies-rspec
My background is Java and I am new to Ruby. I saw a mocking/stubbing framework called Mocka. I saw this example test method:
require 'test/unit'
require 'mocha/test_unit'
class MiscExampleTest < Test::Unit::TestCase
# ...
def test_mocking_an_instance_method_on_a_real_object
product = Product.new
product.expects(:save).returns(true)
assert product.save
end
#...
end
What mechanism was used to "automatically" create a mock object of Person class (or object)? Not sure what to Google for.
If it was something like this
product = mock(Product.new)
I'd easily get it.
Thank You! :)
in general, this is referred to as "monkey patching".
ruby has the concept of open classes, so at runtime you can mess around with it.
in the specific case of mocha, i assume that it is this piece of code here: https://github.com/freerange/mocha/blob/a7bc1b53ace895503b4b5d4915382aead4632e3e/lib/mocha/api.rb#L18-L22
This is an example from Goliath:
require 'goliath'
class HelloWorld < Goliath::API
def response(env)
[200, {}, "hello world"]
end
end
How does defining a class and subclassing Goliath::API results in a web server being started? Shouldn't this just define a class, not actually instantiate and execute one?
Goliath uses at_exit, not unlike Sinatra, Minitest, etc.
See some relevant code here, which highlights the additional handling this trick sometimes requires.
I have to write code for an homework and I wish to do TDD from the start.
The homework consists of one ruby file with methods in it, no class.
All examples I find on the Internet test against classes. How could I test the following method?
homework.rb
#!/usr/bin/env ruby
def count_words(str)
# SOME CODE HERE
end
There is an auto-grading system that take one ruby file with the methods defined for the homework as an input. So, I have to write my tests in a separate file (test_homework.rb) or comment out my test before submitting (which I found counter productive...).
How will I test the count_words method using Test:Unit?
Do something like this:
require File.join(File.expand_path(File.dirname(__FILE__)), 'homework.rb')
require "test/unit"
class TestWordCounter < Test::Unit::TestCase
def test_count_words
assert_equal 3, count_words("one two three")
end
end
I'm currently developing a framework that basically executes another application, e.g. rails within the context of another ruby program. My initial attempt was simply to boot the app like this:
def load_app!
# Load the rails application
require './config/application'
# Initialize the rails application
#app = App::Application.initialize!
end
Problem here, is that the framework's requires conflict with the loaded application so the initialize! call never works although it would in a normal ruby program.
So my question is, if anyone knows a method to basically scope this calls into a unit that behaves like a blank RVM environment. So basically a behavior like this:
require 'json'
puts JSON.generate({:name => "test"})
blank_environment do
puts JSON.generate({:name => "test"})
#=> uninitialized constant JSON
require 'json'
puts JSON.generate({:name => "test"})
end
It's not done with undefining or unloading the currently loaded constants because I don't know all of them because I'm using gems that have other dependencies again.
So is there a cool way? Or any other way to handle this?
UPDATE:
Just came across an idea. Why is ruby's require method always requiring for the global scope? Wouldn't it be a very nice feature to actually scope the loaded modules under the the current module?
module ScopeA
require 'json' #> adds support for ScopeA::JSON
# due to normal ruby scoping everything can be called like normal in here
JSON.parse("something")
end
# JSON should not be available here
module ScopeB
require 'yaml'
YAML.parse("something") # but no JSON, of course
end
Doesn't something like this exist? include already has to know the constants...
Thanks in advance!
Well, after some more research it really doesn't seem possible the way I need it.
I now implemented a basic version using distributed ruby, which doesn't quite satisfy me:
require 'drb/drb'
URI = "druby://localhost:8787"
# Blank environment
pid = fork do
Signal.trap("INT") { puts "Stopping Server.."; exit }
class Application
def call(env)
[200,{},""]
end
end
DRb.start_service(URI, Application.new)
DRb.thread.join
end
# Working environment
DRb.start_service
app = DRbObject.new_with_uri(URI)
puts app.call({})
Process.kill("INT", pid)
Process.wait
If anyone comes up with a better approach, it's highly appreciated!