Why is my rake task running twice in my test? - ruby-on-rails

I have a rake task test that I setup following the only examples I could find online.
It looks like this:
require 'test_helper'
require 'minitest/mock'
require 'rake'
class TestScrapeWelcome < ActiveSupport::TestCase
def setup
Rake.application.init
Rake.application.load_rakefile
#task = Rake::Task['scrape:scrape']
#task.reenable
end
def teardown
Rake::Task.clear
end
test "scraping text and sending to elasticsearch" do
mocked_client = Minitest::Mock.new
get_fixtures.each_with_index do |arg,i|
mocked_client.expect :index, :return_value, [index: "test", type: 'welcome', id: i, body: arg]
end
Elasticsearch::Model.stub :client, mocked_client do
#task.invoke
end
assert mocked_client.verify
end
private
def get_fixtures
(0..11).map { |i|
File.read("test/fixtures/scrape/index_#{i}.json")
}
end
end
But after the task runs once it starts running again without me doing anything (puts prints before and after #task.invoke show that the task is only run the once).

Turns out that rake is already required and initialized when the test runs so all of the following lines need to be removed or the task gets defined twice and runs twice even if you only invoke it once.
require 'minitest/mock'
require 'rake'
...
Rake.application.init
Rake.application.load_rakefile

Updated answer for rails 5.1 (using minitest):
I found I needed the following to load tasks once and only once:
MyAppName::Application.load_tasks if Rake::Task.tasks.empty?
Alternatively add MyAppName::Application.load_tasks to your test_helper, if you don't mind tasks being loaded even when running individual tests that don't need them.
(Replace MyAppName with your application name)

I've tried #iheggie answer but it worked in a way that indeed tests were run once but any other task was breaking with Don't know how to build task '<task_name_like_db_migrate>'.
I'm on Rails 3.2 still. It turned out that there were couple tasks loaded beforehand so the Rake::Task.tasks.empty? was never true and all other useful tasks were not loaded. I've fiddled with it and this version of it works for me right now:
Rake::Task.clear if Rails.env.test?
MyAppName::Application.load_tasks
Hope this helps anyone.

A solution that works for testing the tasks of a Gem that has been made a Railtie so it can add tasks to the Rails app:
Don't define the Railtie in test mode when you're also defining a Rails::Application class in spec_helper.rb (which allows your tests to call Rails.application.load_tasks). Otherwise the Rake file will be loaded once as a Railtie and once as an Engine:
class Railtie < Rails::Railtie
rake_tasks do
load 'tasks/mygem.rake'
end
end unless Rails.env.test? # Without this condition tasks under test are run twice
Another solution would be to put a condition in the Rake file to skip the task definitions if the file has already been loaded.

Related

How to run rake db:migrate with sidekiq

I am trying to run the command rake db:migrate using a sidekiq worker but it seems like it just won't work and I am curious if there is a way to do this or not. I am creating a scaffold using sidekiq but cannot migrate it afterwards
This works
class ScaffoldGeneratorWorker
include Sidekiq::Worker
def perform(id)
`rails g scaffold test_#{id} title:string body:text slug:string visible:boolean`
end
end
But I cannot get this to run afterwards and work
class DatabaseMigrationWorker
include Sidekiq::Worker
def perform
`rake db:migrate`
end
end
Is this possible, and, if so, how can I get it to work. Any help is greatly appreciated.
First you should load rake tasks, then invoke:
class DatabaseMigrationWorker
include Sidekiq::Worker
def perform
Name_Of_Your_App::Application.load_tasks
Rake::Task['db:migrate'].invoke
end
end
This code automagically loads the Rake tasks for your Rails application without you even knowing how your application is named (this was the case for me). It also makes the code easier to share between various Rails projects.
class MySidekiqTask
include Sidekiq::Worker
def perform
application_name = Rails.application.class.parent_name
application = Object.const_get(application_name)
application::Application.load_tasks
Rake::Task['db:migrate'].invoke
end
end
If you need to invoke the Rake task with parameters, you can simply pass them in through the invoke method (https://www.rubydoc.info/gems/rake/Rake%2FTask:invoke):
Rake::Task['db:migrate'].invoke(params)
Did you try adding require 'rake' at the top of your file?
a possible duplicate of How do I run rake tasks within my rails application

How do I access one of my models from within a ruby script in the /lib/ folder in my Rails 3 app?

I tried putting my script in a class that inherited from my model, like so:
class ScriptName < MyModel
But when I ran rake my_script at the command-line, I got this error:
rake aborted!
uninitialized constant MyModel
What am I doing wrong?
Also, should I name my file my_script.rb or my_script.rake?
Just require the file. I do this in one of my rake tasks (which I name my_script.rake)
require "#{Rails.root.to_s}/app/models/my_model.rb"
Here's a full example
# lib/tasks/my_script.rake
require "#{Rails.root.to_s}/app/models/video.rb"
class Vid2 < Video
def self.say_hello
"Hello I am vid2"
end
end
namespace :stuff do
desc "hello"
task :hello => :environment do
puts "saying hello..."
puts Vid2.say_hello
puts "Finished!"
end
end
But a better design is to have the rake task simply call a helper method. The benefits are that it's easier to scan the available rake tasks, easier to debug, and the code the rake task runs becomes very testable. You could add a rake_helper_spec.rb file for example.
# /lib/rake_helper.rb
class Vid2 < Video
def self.say_hello
"Hello I am vid2"
end
end
# lib/tasks/myscript.rake
namespace :stuff do
desc "hello"
task :hello => :environment do
Vid2.say_hello
end
end
All I had to do to get this to work was put my requires above the task specification, and then just declare the :environment flag like so:
task :my_script => :environment do
#some code here
end
Just by doing that, gave me access to all my models. I didn't need to require 'active_record' or even require my model.
Just specified environment and all my models were accessible.
I was also having a problem with Nokogiri, all I did was removed it from the top of my file as a require and added it to my Gemfile.

Testing a method defined in a rake task

I want to test a method defined in a rake task.
rake file
#lib/tasks/simple_task.rake
namespace :xyz do
task :simple_task => :environment do
begin
if task_needs_to_run?
puts "Lets run this..."
#some code which I don't wish to test
...
end
end
end
def task_needs_to_run?
# code that needs testing
return 2 > 1
end
end
Now, I want to test this method, task_needs_to_run? in a test file
How do I do this ?
Additional note: I would ideally want test another private method in the rake task as well... But I can worry about that later.
The usual way to do this is to move all actual code into a module and leave the task implementation to be only:
require 'that_new_module'
namespace :xyz do
task :simple_task => :environment do
ThatNewModule.doit!
end
end
If you use environmental variables or command argument, just pass them in:
ThatNewModule.doit!(ENV['SOMETHING'], ARGV[1])
This way you can test and refactor the implementation without touching the rake task at all.
You can just do this:
require 'rake'
load 'simple_task.rake'
task_needs_to_run?
=> true
I tried this myself... defining a method inside a Rake namespace is the same as defining it at the top level.
loading a Rakefile doesn't run any of the tasks... it just defines them. So there is no harm in loading your Rakefile inside a test script, so you can test associated methods.
When working within a project with a rake context (something like this) already defined:
describe 'my_method(my_method_argument)' do
include_context 'rake'
it 'calls my method' do
expect(described_class.send(:my_method, my_method_argument)).to eq(expected_results)
end
end

Rails plugin require issues

I have a problem creating a Rails plugin, lets call it Mplug. The plugin is pretty much only a rake task, but with a library that the rake task uses.
The problem is to require files. Lets say that this is the rake task:
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
This will not recognize the constant Mplug. So I thought I needed to require it.
require 'mplug'
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
But then I get this message.
no such file to load -- mplug
So, ok. Lets try to give the path to the plugin then.
require 'vendor/plugins/mplug/lib/mplug'
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
This actually works. However, except that I guess that this is a bad way to do it, I now have to require the files in my plugin as if I was in the rails root. For example:
module Mplug
end
require 'mplug/indexer'
Now has to be:
module Mplug
end
require 'vendor/plugins/mplug/lib/mplug/indexer'
Which I do not want to do of course.
Is there any neat way to solve this?
Thanks!
The easiest solution to this problem would be to register the rake task using the Rails::Railtie API. In lib/mplug.rb, define your Railtie:
module Mplug
class Railtie < ::Rails::Railtie
rake_tasks do
load "mplug/rails.rake"
end
end
end
Then, in lib/mplug/rails.rake:
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
Then, make sure your plugin is defined in your Gemfile. If your plugin is in vendor/plugins, add this line to your Gemfile:
gem "mplug", :path => "vendor/plugins/mplug"
If you push the plugin to a git repo, use :git.
Now, rake mplug:create will be available! If you want it to show up in rake -T, make sure you add a description:
namespace :mplug do
desc "creating an mplug"
task :create do
Mplug::Indexer.new
end
end
One option is to use the FILE constant, and then provide the rest of the path relative to the current file:
require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'mplug')
(if your rake task file is in your plugin_root/tasks...)

How to load db:seed data into test database automatically?

I'm attempting to use the new standard way of loading seed data in Rails 2.3.4+, the db:seed rake task.
I'm loading constant data, which is required for my application to really function correctly.
What's the best way to get the db:seed task to run before the tests, so the data is pre-populated?
The db:seed rake task primarily just loads the db/seeds.rb script. Therefore just execute that file to load the data.
load "#{Rails.root}/db/seeds.rb"
# or
Rails.application.load_seed
Where to place that depends on what testing framework you are using and whether you want it to be loaded before every test or just once at the beginning. You could put it in a setup call or in a test_helper.rb file.
I'd say it should be
namespace :db do
namespace :test do
task :prepare => :environment do
Rake::Task["db:seed"].invoke
end
end
end
Because db:test:load is not executed if you have config.active_record.schema_format = :sql (db:test:clone_structure is)
Putting something like this in lib/tasks/test_seed.rake should invoke the seed task after db:test:load:
namespace :db do
namespace :test do
task :load => :environment do
Rake::Task["db:seed"].invoke
end
end
end
I believe Steve's comment above should be the correct answer. You can use Rails.application.load_seed to load seed data into your test envoironment. However, when and how often this data is loaded depends on a few things:
Using Minitest
There is no convenient way to run this file once before all tests (see this Github issue). You'll need to load the data once before each test, likely in the setup method of your test files:
# test/models/my_model_test.rb
class LevelTest < ActiveSupport::TestCase
def setup
Rails.application.load_seed
end
# tests here...
end
Using RSpec
Use RSpec's before(:all) method to load seed data for all test for this model:
describe MyModel do
before(:all) do
Rails.application.load_seed
end
describe "my model..." do
# your tests here
end
Hope this helps.
Building on Matt's answer, if taking that sort of route, I recommend calling Rails.application.load_seed in a before(:suite) block in rspec_helper.rb rather than in a before(:all) block in any file. That way the seeding code is invoked only once for the entire test suite rather than once for each group of tests.
rspec_helper.rb:
RSpec.configure do |config|
...
config.before(:suite) do
Rails.application.load_seed
end
...
end
We're invoking db:seed as a part of db:test:prepare, with:
Rake::Task["db:seed"].invoke
That way, the seed data is loaded once for the entire test run, and not once per test class.
Adding Rake::Task["db:seed"].invoke to the db:test:prepare rake task did not work for me. If I prepared the database with rake db:test:prepare, and then entered the console within the test environment, all my seeds were there. However, the seeds did not persist between my tests.
Adding load "#{Rails.root}/db/seeds.rb" to my setup method worked fine, though.
I would love to get these seeds to load automatically and persist, but I haven't found a way to do that yet!
For those using seedbank, it changes how seeds are loaded, so you probably can't/don't want to use the load ... solution provided here.
And just putting Rake::Task['db:seed'].invoke into test_helper resulted in:
Don't know how to build task 'db:seed' (RuntimeError)
But when we added load_tasks before that, it worked:
MyApp::Application.load_tasks
Rake::Task['db:seed'].invoke

Resources