Printing Rails test names to find the slowest tests - ruby-on-rails

I'd like to find which of the tests are the slowest in my test suite based on this blog post. Here's the minified version of the code:
# test/test_time_tracking.rb
module TestTimeTracking
class ActiveSupport::TestCase
setup :mark_test_start_time
teardown :record_test_duration
def mark_test_start_time
#start_time = Time.now
end
def record_test_duration
puts "Test class: #{self.class.name}"
puts "Duration: #{Time.now - #start_time}"
end
end
end
# test/test_helper.rb
require 'test_time_tracking'
include TestTimeTracking
# ...
Is there a way to print out the test name during either the settup or teardown? In the blog post they call name attribute in the teardown block, but this throws an error in my case. I've also tried #name and #method_name with no success.
I'm using shoulda-contexts gem on top of the default Rails test framework. I know that I can get the test name and duration with rake test TESTOPTS=-v, but I will then have to run another script to parse the output.

Use minitest-reporters. Installation Guide is provided on this page.
After configration use rspec reporter. i.e in your test_helper.rb file write
Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new()]
And run the test. This 'll format output like this:
You can see the time taken by each test.

def record_test_duration
puts "Test class: #{self.class.name}"
puts "Test method: #{self.method_name}"
puts "Duration: #{Time.now - #start_time}"
end
self.method_name will print the current test method name

Rails 5
bin/rails test -v
It prints something like this:
SimpleTest#test_: Simple should be a success. = 0.26 s = .
SimpleTest#test_: Simple should be a failure. = 0.23 s = .

Related

How should I stub a method globally using RSpec?

I am working on a Rails application. I am trying to stub a method globally.
What I am doing is to stub it inside the RSpec configuration, on a before(:suite) block as follows:
RSpec.configure do |config|
config.before(:suite) do
allow_any_instance_of(MyModel).to receive(:my_method).and_return(false)
end
end
However, starting the test fails with the following error:
in `method_missing': undefined method `allow_any_instance_of' for #<RSpec::Core::ExampleGroup:0x00000008d6be08> (NoMethodError)
Any clue? How should I stub a method globally using RSpec?
P.
It probably is a context / initialization issue. Doing it in config.before(:each) should solve your problem.
Do not stub methods in before(:suite) because stubs are cleared after each example, as stated in the rspec-mocks README:
Use before(:each), not before(:all)
Stubs in before(:all) are not supported. The reason is that all stubs
and mocks get cleared out after each example, so any stub that is set
in before(:all) would work in the first example that happens to run in
that group, but not for any others.
Instead of before(:all), use before(:each).
I think that's why allow_any_instance_of is not available in before(:suite) block, but is available in before(:each) block.
If the method is still missing, maybe you configured rspec-mocks to only allow :should syntax. allow_any_instance_of was introduced in RSpec 2.14 with all the new :expect syntax for message expectations.
Ensure that this syntax is enabled by inspecting the value of RSpec::Mocks.configuration.syntax. It is an array of the available syntaxes in rspec-mocks. The available syntaxes are :expect and :should.
RSpec.configure do |config|
config.mock_with :rspec do |mocks|
mocks.syntax = [:expect, :should]
end
end
Once configured properly, you should be able to use allow_any_instance_of.
I recently ran into a case where I needed to stub something in a before(:all) or before(:context) block, and found the solutions here to not work for my use case.
RSpec docs on before() & after() hooks says that it's not supported:
before and after hooks can be defined directly in the example groups they
should run in, or in a global RSpec.configure block.
WARNING: Setting instance variables are not supported in before(:suite).
WARNING: Mocks are only supported in before(:example).
Note: the :example and :context scopes are also available as :each and
:all, respectively. Use whichever you prefer.
Problem
I was making a gem for writing a binary file format which contained at unix epoch timestamp within it's binary header. I wanted to write RSpec tests to check the output file header for correctness, and compare it to a test fixture binary reference file. In order to create fast tests I needed to write the file out once before all the example group blocks would run. In order to check the timestamp against the reference file, I needed to force Time.now() to return a constant value. This led me down the path of trying to stub Time.now to return my target value.
However, since rspec/mocks did not support stubbing within a before(:all) or before(:context) block it didn't work. Writing the file before(:each) caused other strange problems.
Luckily, I stumbled across issue #240 of rspec-mocks which had the solution!
Solution
Since January 9th 2014 (rspec-mocks PR #519) RSpec now contains a method to work around this:
RSpec::Mocks.with_temporary_scope
Example
require 'spec_helper'
require 'rspec/mocks'
describe 'LZOP::File' do
before(:all) {
#expected_lzop_magic = [ 0x89, 0x4c, 0x5a, 0x4f, 0x00, 0x0d, 0x0a, 0x1a, 0x0a ]
#uncompressed_file_data = "Hello World\n" * 100
#filename = 'lzoptest.lzo'
#test_fixture_path = File.join(File.dirname(__FILE__), '..', 'fixtures', #filename + '.3')
#lzop_test_fixture_file_data = File.open( #test_fixture_path, 'rb').read
#tmp_filename = File.basename(#filename)
#tmp_file_path = File.join( '', 'tmp', #tmp_filename)
# Stub calls to Time.now() with our fake mtime value so the mtime_low test against our test fixture works
# This is the mtime for when the original uncompressed test fixture file was created
#time_now = Time.at(0x544abd86)
}
context 'when given a filename, no options and writing uncompressed test data' do
describe 'the output binary file' do
before(:all) {
RSpec::Mocks.with_temporary_scope do
allow(Time).to receive(:now).and_return(#time_now)
# puts "TIME IS: #{Time.now}"
# puts "TIME IS: #{Time.now.to_i}"
my_test_file = LZOP::File.new( #tmp_file_path )
my_test_file.write( #uncompressed_file_data )
#test_file_data = File.open( #tmp_file_path, 'rb').read
end
}
it 'has the correct magic bits' do
expect( #test_file_data[0..8].unpack('C*') ).to eq #expected_lzop_magic
end
## [...SNIP...] (Other example blocks here)
it 'has the original file mtime in LZO file header' do
# puts "time_now= #{#time_now}"
if #test_file_data[17..21].unpack('L>').first & LZOP::F_H_FILTER == 0
mtime_low_start_byte=25
mtime_low_end_byte=28
mtime_high_start_byte=29
mtime_high_end_byte=32
else
mtime_low_start_byte=29
mtime_low_end_byte=32
mtime_high_start_byte=33
mtime_high_end_byte=36
end
# puts "start_byte: #{start_byte}"
# puts "end_byte: #{end_byte}"
# puts "mtime_low: #{#test_file_data[start_byte..end_byte].unpack('L>').first.to_s(16)}"
# puts "test mtime: #{#lzop_test_fixture_file_data[start_byte..end_byte].unpack('L>').first.to_s(16)}"
mtime_low = #test_file_data[mtime_low_start_byte..mtime_low_end_byte].unpack('L>').first
mtime_high = #test_file_data[mtime_high_start_byte..mtime_high_end_byte].unpack('L>').first
# The testing timestamp has no high bits, so this test should pass:
expect(mtime_low).to eq #time_now.to_i
expect(mtime_high).to eq 0
expect(mtime_low).to eq #lzop_test_fixture_file_data[mtime_low_start_byte..mtime_low_end_byte].unpack('L>').first
expect(mtime_high).to eq #lzop_test_fixture_file_data[mtime_high_start_byte..mtime_high_end_byte].unpack('L>').first
mtime_fixed = ( mtime_high << 16 << 16 ) | mtime_low
# puts "mtime_fixed: #{mtime_fixed}"
# puts "mtime_fixed: #{mtime_fixed.to_s(16)}"
expect(mtime_fixed).to eq #time_now.to_i
end
end
end
end
If you want a particular method to behave a certain way for your entire test suite, there's no reason to even deal with RSpec's stubs. Instead, you can simply (re)define the method to behave how you want in your test environment:
class MyModel
def my_method
false
end
end
This could go in spec/spec_helper.rb or a similar file.
What version of RSpec are you using? I believe allow_any_instance_of was introduced in RSpec 2.14. For earlier versions, you can use:
MyModel.any_instance.stub(:my_method).and_return(false)
You may use the following to stub a method 'do_this' of class 'Xyz' :
allow_any_instance_of(Xyz).to receive(:do_this).and_return(:this_is_your_stubbed_output)
This stubs the output to - ':this_is_your_stubbed_output' from wherever this function is invoked.
You may use the above piece of code in before(:each) block to make this applicable for all your spec examples.

Cant get helper test to run - Rails/Rspec

I am trying to test a helper in a Rails application. I am new at rspec and am having a hard time getting the test to run. Here is my test file:
require 'spec_helper'
describe "ServiceHoursHelpers" do
include ServiceHoursHelper
describe "test" do
it "should equal Jason" do
test.should eql("Test")
end
end
end
And here is my test file:
module ServiceHoursHelper
def test
"Test"
end
end
Here is the command I am running
rspec spec/helpers/service_hours_helper_spec.rb
It isnt succeeding and there are no errors, so obviously I am not hitting the code. What is it that I am missing?
Try run your test method like this:
helper.test.should eql("Test")

testing using Resque with Rspec examples?

I am processing my background jobs using Resque.
My model looks like this
class SomeClass
...
repo = Repo.find(params[:repo_id])
Resque.enqueue(ReopCleaner, repo.id)
...
end
class RepoCleaner
#queue = :repo_cleaner
def self.perform(repo_id)
puts "this must get printed in console"
repo = Repo.find(repo_id)
# some more action here
end
end
Now to test in synchronously i have added
Resque.inline = Rails.env.test?
in my config/initializers/resque.rb file
This was supposed to call #perform method inline without queuing it into Redis and without any Resque callbacks as Rails.env.test? returns true in test environment.
But
"this must get printed in console"
is never printed while testing. and my tests are also failing.
Is there any configurations that i have missed.
Currently i am using
resque (1.17.1)
resque_spec (0.7.0)
resque_unit (0.4.0)
I personally test my workers different. I use RSpec and for example in my user model I test something like this:
it "enqueue FooWorker#create_user" do
mock(Resque).enqueue(FooWorker, :create_user, user.id)
user.create_on_foo
end
Then I have a file called spec/workers/foo_worker_spec.rb with following content:
require 'spec_helper'
describe FooWorker do
describe "#perform" do
it "redirects to passed action" do
...
FooWorker.perform
...
end
end
end
Then your model/controller tests run faster and you don't have the dependency between model/controller and your worker in your tests. You also don't have to mock so much things in specs which don't have to do with the worker.
But if you wan't to do it like you mentioned, it worked for me some times ago. I put Resque.inline = true into my test environment config.
It looks like the question about logging never got answered. I ran into something similar to this and it was from not setting up the Resque logger. You can do something as simple as:
Resque.logger = Rails.logger
Or you can setup a separate log file by adding this to your /lib/tasks/resque.rake. When you run your worker it will write to /log/resque.log
Resque.before_fork = Proc.new {
ActiveRecord::Base.establish_connection
# Open the new separate log file
logfile = File.open(File.join(Rails.root, 'log', 'resque.log'), 'a')
# Activate file synchronization
logfile.sync = true
# Create a new buffered logger
Resque.logger = ActiveSupport::Logger.new(logfile)
Resque.logger.level = Logger::INFO
Resque.logger.info "Resque Logger Initialized!"
}
Mocking like daniel-spangenberg mentioned above ought to write to STDOUT unless your methods are in the "private" section of your class. That's tripped me up a couple times when writing rspec tests. ActionMailer requires it's own log setup too. I guess I've been expecting more convention than configuration. :)

Testing a patch to the Rails mysql adapter

I wrote a little monkeypatch to the Rails MySQLAdapter and want to package it up to use it in my other projects. I am trying to write some tests for it but I am still new to testing and I am not sure how to test this. Can someone help get me started?
Here is the code I want to test:
unless RAILS_ENV == 'production'
module ActiveRecord
module ConnectionAdapters
class MysqlAdapter < AbstractAdapter
def select_with_explain(sql, name = nil)
explanation = execute_with_disable_logging('EXPLAIN ' + sql)
e = explanation.all_hashes.first
exp = e.collect{|k,v| " | #{k}: #{v} "}.join
log(exp, 'Explain')
select_without_explain(sql, name)
end
def execute_with_disable_logging(sql, name = nil) #:nodoc:
#Run a query without logging
#connection.query(sql)
rescue ActiveRecord::StatementInvalid => exception
if exception.message.split(":").first =~ /Packets out of order/
raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
else
raise
end
end
alias_method_chain :select, :explain
end
end
end
end
Thanks.
General testing
You could start reading about testing.
After you are understanding the basics of testing, you should think what you have changed. Then make some tests which test for
the original situation, resulting in errors since you updated it. So reverse the test after it indeed is working for the original situation.
the new situation to see whether you have implemented your idea correctly
The hardest part is to be sure that you covered all situations. Finally, if both parts pass then you could say that your code it working as expected.
Testing gems
In order to test gems you can run
rake test:plugins
to test all plugins of your rails application (see more in chapter 6 of the testing guide), this only works when the gem is in the vendor directory of an application.
Another possibility is to modify the Rakefile of the gem by including a testing task. For example this
desc 'Test my custom made gem.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
would run all available tests in the test directory ending with _test.rb. To execute this test you can type rake test (from the gem directory!).
In order to run the tests for the gem by default (when typing just rake) you can add/modify this line:
task :default => :test
I used the second method in my ruby-bbcode gem, so you could take a look at it to see the complete example.

How to invoke a rake task from within a test program

I am not trying to test rake tasks. I have a test program which sends out emails ( real emails yes) to test email templates etc.
class EmailTemplatesTest < ActiveSupport::TestCase
context 'send_password_info' do
setup do
Emailtb.send_password_info(user)
Rake::Task['email:run'].invoke # this actually delivers email
end
should 'have one emailtb' do
assert_equal 1, Emailtb.count
end
end
end
When I run this test then I get following error.
RuntimeError: Don't know how to build task 'email:run'
However if I run the rake task separately then it works fine
rake email:run
The test environment doesn't load files in lib. You have to manually load them at the top of the file, like so:
require 'rake'
load File.join(RAILS_ROOT, 'lib', 'tasks', 'my_task.rake')
class EmailTemplatesTest < ActiveSupport::TestCase
context 'send_password_info' do
setup do
Emailtb.send_password_info(user)
Rake::Task['email:run'].invoke # this actually delivers email
end
should 'have one emailtb' do
assert_equal 1, Emailtb.count
end
end
end

Resources