Where to put helper functions for rake tasks and test files in Ruby on Rails? - ruby

In my Rails application I have a file sample_data.rb inside /lib/tasks as well as a bunch of test files inside my /spec directory.
All these files often share common functionality such as:
def random_address
[Faker::Address.street_address, Faker::Address.city].join("\n")
end
Where should I put those helper functions? Is there some sort of convention on this?
Thanks for any help!

You could create a static class, with static functions. That would look something like this:
class HelperFunctions
def self.random_address
[Faker::Address.street_address, Faker::Address.city].join("\n")
end
def self.otherFunction
end
end
Then, all you would need to do is:
include your helper class in the file you want to use
execute it like:
HelperFunctions::random_address(anyParametersYouMightHave)
When doing this, make sure you include any dependencies in your HelperFunctions class.

If you're sure it's rake only specific, you also can add in directly in RAILS_ROOT/Rakefile (that's probably not the case for the example you use).
I use this to simplify rake's invoke syntax :
#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require File.expand_path('../config/application', __FILE__)
def invoke( task_name )
Rake::Task[ task_name ].invoke
end
MyApp::Application.load_tasks
That way, I can use invoke "my_namespace:my_task" in rake tasks instead of Rake::Task[ "my_namespace:my_task" ].invoke.

You share methods in a module, and you place such a module inside the lib folder.
Something like lib/fake_data.rb containing
module FakeData
def random_address
[Faker::Address.street_address, Faker::Address.city].join("\n")
end
module_function
end
and inside your rake task just require the module, and call FakeData.random_address.
But, if it is like a seed you need to do every time you run your tests, you should consider adding this to your general before all.
E.g. my spec_helper looks like this:
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
config.use_transactional_fixtures = true
config.infer_base_class_for_anonymous_controllers = false
config.order = "random"
include SetupSupport
config.before(:all) do
load_db_seed
end
end
and the module SetupSupport is defined in spec/support/setup_support.rb and looks as follows:
module SetupSupport
def load_db_seed
load(File.join(Rails.root, 'db', 'seeds.rb'))
end
end
Not sure if you need to load the seeds, or are already doing this, but this is the ideal spot to also generate needed fake data.
Note that my setup support class is defined in spec/support because the code is only relevant to my specs, I have no rake task also needing the same code.

Related

Stub require statement in rspec?

I have to maintain a Ruby script, which requires some libs I don't have locally and which won't work in my environment. Nevertheless I want to spec some methods in this script so that I can change them easily.
Is there an option to stub some of the require statements in the script I want to test so that it can be loaded by rspec and the spec can be executed within my environment?
Example (old_script.rb):
require "incompatible_lib"
class Script
def some_other_stuff
...
end
def add(a,b)
a+b
end
end
How can I write a test to check the add function without splitting the "old_Script.rb" file and without providing the incompatible_lib I don't have?
Instead of stubbing require which is "inherited" from Kernel, you could do this:
Create a dummy incompatible_lib.rb file somewhere that is not in your $LOAD_PATH. I.e., if this is a Ruby application (not Rails), don't put it in lib/ nor spec/.
You can do this a number of ways, but I'll tell you one method: in your spec file which tests Script, modify $LOAD_PATH to include the parent directory of your dummy incompatible_lib.rb.
Ordering is very important -- next you will include script.rb (the file which defines Script).
This will get you around the issue and allow you test test the add method.
Once you've successfully tested Script, I would highly recommend refactoring it so that you don't have to do this technique, which is a hack, IMHO.
Thanks, I also thought about the option of adding the files, but finally hacked the require itself within the test case:
module Kernel
alias :old_require :require
def require(path)
old_require(path) unless LIBS_TO_SKIP.include?(path)
end
end
I know that this is an ugly hack but as this is legacy code executed on a modified ruby compiler I can't easily get these libs running and it's sufficient to let me test my modifications...

Anyway to reload the modified gem files in the REPL

While trying to build a Ruby gem (using Bundler), I tend to test the code using the REPL provided by Bundler - accessible via bundle console.
Is there any way to reload the entire project in it? I end up loading individual (changed) files again to test the new change.
The following hack works for a relatively simple gem of mine and Ruby 2.2.2. I'll be curious to see if it works for you. It makes the following assumptions:
You have the conventional folder structure: a file called lib/my_gem_name.rb and a folder lib/my_gem_name/ with any files / folder structure underneath.
All the classes you want to reload are nested within your top module MyGemName.
It will probably not work if in one of the files under lib/my_gem_name/ you monkey-patch classes outside of your MyGemName namespace.
If you're good with the assumptions above, put the following code inside the module definition in lib/my_gem_name.rb and give it a try:
module MyGemName
def self.reload!
Reloader.new(self).reload
end
class Reloader
def initialize(top)
#top = top
end
def reload
cleanup
load_all
end
private
# #return [Array<String>] array of all files that were loaded to memory
# under the lib/my_gem_name folder.
# This code makes assumption #1 above.
def loaded_files
$LOADED_FEATURES.select{|x| x.starts_with?(__FILE__.chomp('.rb'))}
end
# #return [Array<Module>] Recursively find all modules and classes
# under the MyGemName namespace.
# This code makes assumption number #2 above.
def all_project_objects(current = #top)
return [] unless current.is_a?(Module) and current.to_s.split('::').first == #top.to_s
[current] + current.constants.flat_map{|x| all_project_objects(current.const_get(x))}
end
# #return [Hash] of the format {Module => true} containing all modules
# and classes under the MyGemName namespace
def all_project_objects_lookup
#_all_project_objects_lookup ||= Hash[all_project_objects.map{|x| [x, true]}]
end
# Recursively removes all constant entries of modules and classes under
# the MyGemName namespace
def cleanup(parent = Object, current = #top)
return unless all_project_objects_lookup[current]
current.constants.each {|const| cleanup current, current.const_get(const)}
parent.send(:remove_const, current.to_s.split('::').last.to_sym)
end
# Re-load all files that were previously reloaded
def load_all
loaded_files.each{|x| load x}
true
end
end
end
If you don't want this functionality to be available in production, consider monkey-patching this in the bin/console script, but make sure to change the line $LOADED_FEATURES.select{|x| x.starts_with?(__FILE__.chomp('.rb'))} to something that will return a list of relevant loaded files given the new location of the code.
If you have a standard gem structure, this should work:
$LOADED_FEATURES.select{|x| x.starts_with?(File.expand_path('../../lib/my_gem_name'))} (make sure to put your monkey patching code before the IRB.start or Pry.start)

How to define a simple global variable in an rspec test that can be accesed by helper functions

I cant figure out how to use a simple global variable in an rspec test. It seems like such a trivial feature but after much goggleing I havent been able to find a solution.
I want a variable that can be accessed/changed throughout the main spec file and from functions in helper spec files.
Here is what I have so far:
require_relative 'spec_helper.rb'
require_relative 'helpers.rb'
let(:concept0) { '' }
describe 'ICE Testing' do
describe 'step1' do
it "Populates suggestions correctly" do
concept0 = "tg"
selectConcept() #in helper file. Sets concept0 to "First Concept"
puts concept0 #echos tg?? Should echo "First Concept"
end
end
.
#helpers.rb
def selectConcept
concept0 = "First Concept"
end
Can someone point out what I am missing or if using "let" is totally the wrong method?
Consider using a global before hook with an instance variable: http://www.rubydoc.info/github/rspec/rspec-core/RSpec/Core/Configuration
In your spec_helper.rb file:
RSpec.configure do |config|
config.before(:example) { #concept0 = 'value' }
end
Then #concept0 will be set in your examples (my_example_spec.rb):
RSpec.describe MyExample do
it { expect(#concept0).to eql('value') } # This code will pass
end
It turns out the easiest way is to use a $ sign to indicate a global variable.
See Preserve variable in cucumber?
This is an old thread, but i had this question today. I just needed to define a long string to stub out a command that is in multiple files as:
# in each spec file that needed it
let(:date_check) do
<<~PWSH.strip
# lots of powershell code
PWSH
end
# in any context in that file (or a shared context)
before(:each) do
stub_command(date_check).and_return(false)
end
Searched, Stack Overflow, etc, landed on this: Note the usage of the variable doesn't change at all! (Assumes all specs require 'spec_helper')
# in spec_helper.rb
def date_check
<<~PWSH.strip
# lots of powershell code
PWSH
end
# in any context in any spec file
before(:each) do
stub_command(date_check).and_return(false)
end
I suggest you define the variable in the helper file, where it can be used by other helper code, and can be accessed from your tests.
For my project, I wanted to keep all the setup stuff in spec_helper.rb, and use those settings, plus any custom variables and methods in the tests. The following, modified from the RSpec-core 3.10 docs, is not Rails-specific.
Create a new setting for RSpec.configure called my_variable, and give it a value, like this:
# spec/spec_helper.rb
RSpec.configure do |config|
config.add_setting :my_variable
config.my_variable = "Value of my_variable"
end
Access settings as a new read-only property in RSpec.configuration from your test:
# spec/my_spec.rb
RSpec.describe(MyModule) do
it "creates an instance of something" do
my_instance = MyModule::MyClass.new(RSpec.configuration.my_variable)
end
end

How to really reset a unit test?

I'd like to test class and gem loading. Have a look at the following stupid test case:
require 'rubygems'
require 'shoulda'
class SimpleTest < Test::Unit::TestCase
context 'This test' do
should 'first load something' do
require 'bundler'
assert Object.const_defined? :Bundler
end
should 'second have it reset again' do
assert !Object.const_defined?(:Bundler)
end
teardown do
# This works, but is tedious and unclean
#Object.send :remove_const, :Bundler rescue nil
# Instead I want something like this ;)
magic_reset
end
end
end
How about creating a subclass of Test::Unit::TestCase which runs the test method in a forked process?
class ForkingTestCase < Test::Unit::TestCase
def run(...)
fork do
super.run(...)
# somehow communicate the result back to the parent process
# this is the hard part
end
end
end
If this can be implemented, it should then just be a matter of changing the base class of your test case.
AFAIK, you cannot unload a file that you have loaded. You need to start a separate Ruby process for every test. (Or a separate Ruby instance if you are running on a Ruby implementation which supports multiple instances in the same process.)
Try using Kernel#load with wrap set to true:
load(filename, wrap=false) → true
Loads and executes the Ruby program in the file filename. If the
filename does not resolve to an absolute path, the file is searched
for in the library directories listed in $:. If the optional wrap
parameter is true, the loaded script will be executed under an
anonymous module, protecting the calling program’s global namespace.
In no circumstance will any local variables in the loaded file be
propagated to the loading environment.
each time you want to do a test of bundler, load it into a new anonymous module, do your tests on the bundler class within that module, and then go onto your next test.
If your code refers to the Bundler constant, then you'd have to set and unset that constant, though.
I haven't tried it myself, but can't see why it wouldn't work.
Try keeping track of what constants were defined before your tests started, and what constants are defined after your test finished, and remove the constants that were defined during the test.
I guess this isn't so much telling you how to call magic_reset as how to implement magic_reset.

in ruby, how can I know what module is defined as result of a 'load' or 'require'?

In ruby, if I do "require foo", is there a way to subsequently determine the name of the module or modules defined in foo.rb?
For example, say I have a ruby file named foo.rb that looks like this:
# foo.rb
module MyModule
def self.summary
"this does something useful"
end
...
end
In another script, after I do "require foo", how can I determine that I now have a module named MyModule?
Ultimate what I'm after is to be able to do something like this:
file = someComputedFileName()
require file
puts "summary of #{file}: #{???::summary}
While I could force myself to make module names and file names identical, I'd rather not. I want a bit more freedom in making short filenames but more expressive module names. However, I am guaranteeing to myself that each file will only define a single module (think: plugins).
I don't know if this is the best solution, but off the top of my head this seems to work:
all_constants = Object.constants
require 'foo'
foo_constants = Object.constants - all_constants
foo_constants should give you only the modules, classes or other constants that were defined by foo.rb.
One approach would be to use ObjectSpace.each_object(Module) to find all defined modules before requiring the file. Then, after you require the file, loop over all defined modules again and see if there are any new ones.
require "set"
old_modules = SortedSet.new
ObjectSpace.each_object(Module) {|m| old_modules.add(m) }
file = someComputedFileName()
require file
new_modules = SortedSet.new
ObjectSpace.each_object(Module) {|m| new_modules.add(m) unless old_modules.include?(m) }
puts "summary of #{file}: #{new_modules.to_a.map{|m|m.summary}.join(',')}"
That would also let you define more than one module in the file.

Resources