Rspec Rake Task how to parse/simulate user input? - ruby-on-rails

In my rake task I have:
namespace :example do
desc "this does something"
task :something, [:arg1] => :environment do |t, args|
(some_irrelevant_code)
print 'YES/ NO : '
choice = STDIN.gets.chomp.upcase
case choice
when 'YES'
do_something
break
when 'NO'
break
end
end
end
In my spec I have:
require "spec_helper"
require "rake"
feature "Example" do
before do
load File.expand_path("../../../lib/tasks/example.rake", __FILE__)
Rake::Task.define_task(:environment)
end
scenario "something" do
Rake.application.invoke_task("example:something[rake_args_here]")
end
All is working fine, although I am having troubles finding a way to avoid having to type the user input in the console when running the test.
Basically I want the test to run and assume that the user is going to type "YES".
Please let me know if you have a solution for this or point me in the right direction.
Thanks in advance.

If you use STDIN, you're stuck, that's a constant. It's worth noting that using STDIN is not recommended because of this limitation.
If you use $stdin, the global variable equivalent and modern replacement, you can reassign it:
require 'stringio'
$stdin = StringIO.new("fake input")
$stdin.gets.chomp.upcase
# => "FAKE INPUT"
That means you can, for testing purposes, rework $stdin. You'll want to put it back, though, which means you need a wrapper like this:
def with_stdin(input)
prev = $stdin
$stdin = StringIO.new(input)
yield
ensure
$stdin = prev
end
So in practice:
with_stdin("fake input") do
puts $stdin.gets.chomp.upcase
end

You should stub STDIN object like this STDIN.stub(gets: 'test')
or
allow(STDIN).to receive(:gets).and_return('test')
If both of them do not work then try:
allow(Kernel).to receive(:gets).and_return('test')

Related

RSpec doesn't show full difference between return and expected

When a run a test with JSON, the rspec doesn't show the full spec, so I can't see the diference between return and expected.
The message of diff is shortened with ...
expected: "{\"id\":1,\"number\":1,\"sequential\":1,\"emitted_at\":\"2014-01-01T13:35:21.000Z\",\"status\":\"aut...erenceds_attributes\":[{\"id\":null,\"nfe_key\":\"42150707697707000148550010000020101000020105\"}]}"
got: "{\"id\":1,\"number\":1,\"sequential\":1,\"emitted_at\":\"2014-01-01T13:35:21.000Z\",\"status\":\"aut...erenceds_attributes\":[{\"id\":null,\"nfe_key\":\"42150707697707000148550010000020101000020105\"}]}"
aut...erenceds_attributes look in middle of message
My script test:
RSpec.describe InvoiceSerializer do
let(:invoice) do
build :invoice, :testing_serializer
end
subject { described_class.new invoice }
it "returns a json" do
expected = {
id: 1,
number: 1,
sequential: 1,
emitted_at: "2014-01-01T13:35:21.000Z",
status: "authorized",
invoice_bills_attributes: [{
id: nil,
expire_at: "2014-01-02T00:00:00.000Z",
value: "1.23"
}],
...
}.to_json
expect(subject.to_json).to eq expected
end
end
Example of error in my console
What gem/plugin or expectation that you use to check your test?
I use the console and Rubymine IDE.
Now I use:
puts "1 --> #{subject.to_json}"
puts "2 --> #{expected}"
And I don't like to write this for to debbug my test.
Set RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length to a high value
Update: as Yurri suggested, it might be better to better to set it to nil
This might help: https://github.com/waterlink/rspec-json_expectations
As a bonus, it allows you to specify your tests in terms of a subset of attributes, which can be used to create more granular tests.
To build on previous answers, and utilize the RSpec.configure syntax you'll want to use something like this:
RSpec.configure do |rspec|
rspec.expect_with :rspec do |c|
# Or a very large value, if you do want to truncate at some point
c.max_formatted_output_length = nil
end
end

Stubbing key/value pair in ruby on rails ENV

I want to test the effect of the value of an ENV key on my code. I am stubbing this by using
allow(ENV).to receive(:[]).with('ADWORDS_RUN').and_return('No')
This was working until I changed the target code to include accessing another ENV key. The target code now includes the following
def not_local_machine?
!ENV['LOCAL_MACHINE']
end
The test now fails in the above function with the error message
Failure/Error: get 'home'
ENV received :[] with unexpected arguments
expected: ("ADWORDS_RUN")
got: ("LOCAL_MACHINE")
Please stub a default value first if message might be received with other args as well.
It appears that my current method of stubbing is wiping out other ENV keys. How do I stub an ENV key to avoid this problem?
You can use
stub_const 'ENV', ENV.to_h.merge('ADWORDS_RUN' => 'No')
This is how I solved that issue:
before { allow(ENV).to receive(:[]).and_call_original }
context 'ADWORDS_RUN is No' do
before { allow(ENV).to receive(:[]).with('ADWORDS_RUN').and_return('No') }
[example block]
end
(Aside, I recommend using something like 'false' instead of 'No'.)
For modifying ENV's in tests, Thoughtbot's climate_control gem is worth a look.
You wrap your test around ClimateControl block to control temporary changes to ENV values. Using your example:
ClimateControl.modify ADWORDS_RUN: 'No' do
expect(AdwordsTask.new.run?).to eq(false)
end
To use with RSpec, you could define this in your spec:
def with_modified_env(options, &block)
ClimateControl.modify(options, &block)
end
This would allow for more straightforward way to modify/stub environment values:
require 'spec_helper'
describe AdwordsTask, 'name' do
it 'does not run adwords' do
with_modified_env ADWORDS_RUN: 'No' do
expect(AdwordsTask.new.run?).to eq(false)
end
end
def with_modified_env(options, &block)
ClimateControl.modify(options, &block)
end
end
You are overriding/overwriting the [] method of ENV. The original meaning is gone completely.
Check out https://github.com/rspec/rspec-mocks and look for the chapter "Arbitrary Handling". It contains this sample code:
expect(double).to receive(:msg) do |arg|
expect(arg.size).to eq 7
end
You should be able to adopt that for your needs... something along the lines of (untested)
dummy_env = { ADWORDS_RUN: 1, LOCAL_MACHINE: 2 }
allow(ENV).to receive(:[]) do |key|
dummy_env[key] or raise "#{key} not expected"
end
Or if you want to keep all old ENV entries
env_clone = ENV.clone
allow... do|key|
dummy_env[key] or env_clone[key]
end

Ruby: Default values for define?

I have a question about define my main issue is I am a bit confused on how the parameters work for it.
This is my Methods
def repeat(repeated_word)
#repeated_word = repeated_word
"##repeated_word ##repeated_word"
end
This is my rspec test to make sure my method works.
describe "repeat" do
it "should repeat" do
repeat("hello").should == "hello hello"
end
# Wait a second! How can you make the "repeat" method
# take one *or* two arguments?
#
# Hint: *default values*
it "should repeat a number of times" do
repeat("hello", 3).should == "hello hello hello"
end
end
it passes the first test but fails the second. My confusion is if i add a second parameter meaning def repeat(repeat_word, times_repeated)
the first test then fails because it has the wrong number of arguments. Not sure how to set up default values?
def repeat(repeated_word, repeats=2)
repeats.times.map { repeated_word }.join(' ')
end

Does rake task limit functionality of my class to call external API?

AllegroAPI is a class in the /models directory that calls an external API. It works as I wish when I test in somewhere else not by running rake task.
Example working code:
require "./AllegroAPI"
allegro = AllegroAPI.new(login: 'LOGIN',
password: File.read('XXXX.txt'),
webapikey: File.read('XXX.txt')
)
puts allegro.do_search({"search-string"=>"nokia",
"search-price-from"=>300.0,
"search-price-to"=>500.0,
"search-limit"=>50}).to_s
As I've said it works correctly. It calls the API and prints out the result.
File allegro.rb is also in the models directory and it's a file I'm executing by running this task:
namespace :data do
desc "Update auctions table in database"
task update_auctions: :environment do
Allegro.check_for_new_auctions
end
end
allegro.rb:
module Allegro
require 'AllegroAPI'
def self.check_for_new_auctions
allegro = AllegroAPI.new(login: 'LOGIN',
password: File.read('app/models/ignore/XXXX.txt'),
webapikey: File.read('app/models/ignore/XXX.txt')
)
looks = Look.all
looks.each do |l|
hash_to_ask = ActiveSupport::JSON.decode(l[:look_query]).symbolize_keys
hash_to_ask = hash_to_ask.each_with_object({}) do |(k,v), h|
if v.is_number?
h[k.to_s.split('_').join('-')] = v.to_f
else
h[k.to_s.split('_').join('-')] = v
end
end
results = allegro.do_search(hash_to_ask)
#do something with data
end
end
end
The problem is that it doesn't return anything. var result is not nil, but it does not hold anything.
When I'm trying to debug it and call API from the inside do_search function it's calling API, doesn't raise a error but response is nothing. AllegroAPI works correctly. There is no problem with var "hash_to_ask", it's exactly the same hash as in working example.
EDIT:
I've commented out check_for_new_auctions and used "puts", it works fine when I run it by executing rake task. Then I've used exactly the same code which I used in normal file which have ran properly:
class Allegro
def self.check_for_new_auctions
allegro = AllegroAPI.new(login: 'LOGIN',
password: File.read('app/models/ignore/XXXX.txt'),
webapikey: File.read('app/models/ignore/XXXX.txt')
)
hash_to_ask = {"search-string"=>"nokia",
"search-price-from"=>300.0,
"search-price-to"=>500.0,
"search-limit"=>50}
allegro.do_search(hash_to_ask).to_s
end
end
It have not worked;/ The returned value from allegro.do_search(hash_to_ask) is hash, not empty, not nil but when I try to print it, it's nothing, empty place.
EDIT:
Everything have worked properly, waste like 15 hours total debugging the problem which have not existed. I'm not sure why it have not worked but it couldn't print to the console after converting to string, so I tried writing it down to file blindly. What I have found in the text file? Data.
I don't know why it couldn't print out everything in the console.
In the IRB script that you show, you have some puts statement that is not in your rake task. So for debugging, I would add puts ... to your Rake task, e.g.:
namespace :data do
desc "Update auctions table in database"
task update_auctions: :environment do
puts "Start Auctions..."
results = Allegro.check_for_new_auctions
puts "Results: #{results}"
end
end
Now, when you run:
rake data:update_auctions
You should get some output. Otherwise rinse-and-repeat by adding puts statements in the method that you are calling.

Rails - Help with rake task

I have a rake task I need to run in order to sanitize (remove forward slashes) some data in the database. Here's the task:
namespace :db do
desc "Remove slashes from old-style URLs"
task :substitute_slashes => :environment do
puts "Starting"
contents = Content.all
contents.each do |c|
if c.permalink != nil
c.permalink.gsub!("/","")
c.save!
end
end
puts "Finished"
end
end
Which allows me to run rake db:substitute_slashes --trace
If I do puts c.permalink after the gsub! I can see it's setting the attribute properly. However the save! doesn't seem to be working because the data is not changed. Can someone spot what the issue may be?
Another thing, I have paperclip installed and this task is triggering [paperclip] Saving attachments. which I would rather avoid.
try this:
namespace :db do
desc "Remove slashes from old-style URLs"
task :substitute_slashes => :environment do
puts "Starting"
contents = Content.all
contents.each do |c|
unless c.permalink.nil?
c.permalink = c.permalink.gsub(/\//,'')
c.save!
end
end
puts "Finished"
end
end
1.) Change != nil to unless record.item.nil? (I don't know if it makes a different, but I've never used != nil. You may want to use .blank? also judging by your code)
2.) Your gsub was malformed. The pattern must be between two / (/ stuff /). The \ is necessary because you need to escape the /.
3.) Bang (!) updates the object in place. I think your biggest issue may be that you are overusing !.
4.) You're also making this very inefficient... You are looking at every record and updating every record. Rails isn't always the best option. Learn SQL and do this in one line:
"UPDATE contents SET permalink = replace(permalink, '/', '');"
If you MUST use Rails:
ActiveRecord::Base.connection.execute "UPDATE contents SET permalink = replace(permalink, '/', '');"
Wow! One query. Amazing! :)
The next thing I would try would be
c.permalink = c.permalink.gsub("/","")
As for saving without callbacks, this stackoverflow page has some suggestions.

Resources