I'm trying to test a method that logs multiple messages based on conditionals to a log that is not the default Rails logger. I formatted the logger in config/environment.rb:
# Format the logger
class Logger
def format_message(level, time, progname, msg)
"#{time.to_s(:db)} #{level} -- #{msg}\n"
end
end
and created a new logger in the ImportRecording class in the lib/ directory. A method of that class includes the following:
# some code omitted...
days.each do |day|
if not hash[day].include? "copied"
#log.error "#{day} needs to be copied!"
end
if not hash[day].include? "compressed"
#log.error "#{day} needs to be compressed!"
end
if not hash[day].include? "imported"
#log.debug "#{day} needs to be imported"
`rake RAILS_ENV=#{Rails.env} recordings:import[#{day}]` unless Rails.env == "test"
end
end
# finishing up logging omitted...
I wrote a tiny macro to help with testing this method:
def stub_todo
{ "20130220" => ["copied"],
"20130219" => ["copied", "compressed"],
"20130218" => ["copied", "compressed", "imported"] }
end
and here's my test:
describe ".execute_todo" do
it "carries out the appropriate commands, based on the todo hash" do
ImportRecording.execute_todo stub_todo
ImportRecording.log.should_receive(:debug).with("20130219 needs to be imported")
ImportRecording.log.should_receive(:error).with("20130220 needs to be compressed!")
ImportRecording.log.should_receive(:debug).with("20130220 needs to be imported")
end
end
I stare at the import log as I run these tests and watch the lines get added (there's a delay, because the log is large by now), but the tests still fail. I wonder if the formatting of the log is messing this up, but I am passing the aforementioned strings to the methods :debug and :error to the log. Any help?
EDIT 3/14/13:
In the hopes that someone may be able to help me out here, I changed by test to look as follows:
it "carries out the appropriate commands, based on the todo hash" do
ImportRecording.stub!(:execute_todo).with(stub_todo).and_return(false)
ImportRecording.log.should_receive(:debug).with("20130219 needs to be imported")
ImportRecording.log.should_receive(:error).with("20130220 needs to be compressed!")
ImportRecording.log.should_receive(:debug).with("20130220 needs to be imported")
end
and this is the error I'm getting from RSpec:
Failure/Error: ImportRecording.log.should_receive(:debug).with("20130219 needs to be imported")
(#<Logger:0x007fb04fa83ed0>).debug("20130219 needs to be imported")
expected: 1 time
received: 0 times
I found the problem. Somehow, these expectations are supposed to be declared before the method that should actually cause them. The code should read
it "carries out the appropriate commands, based on the todo hash" do
ImportRecording.log.should_receive(:debug).with("20130219 needs to be imported")
ImportRecording.log.should_receive(:error).with("20130220 needs to be compressed!")
ImportRecording.log.should_receive(:debug).with("20130220 needs to be imported")
ImportRecording.execute_todo stub_todo
end
The tests now pass. I also had to add in more lines to the test to account for every line that was written to the log because of the method call. So for future researchers, state your expectations and then call the method.
I stare at the import log as I run these tests and watch the lines get added
If you set message expectations on the logger calls, you should not see the lines added to the log. Like stubs, message expectations replace the implementation of the original method. This suggests that your logger setup is mis-configured somehow.
Related
I have a setup command that I want executed every time I start the rails console -
MyClass.some_method()
I get tired of retyping it each time I fire up rails c - is there a way to have it automatically get run every time a new console is started?
Thanks!
We do this in order to ask for the tenant every time the console starts. It took a bit of investigation, but we got it working fairly elegantly. Note that this works with Rails 5.2 but it has worked mostly the same way since Rails 4.
Another thing to note is that this is written specifically because we wanted to be able to run the method once on start and then be able to run it again while using the console, say if we wanted to switch the tenant during a session.
The first step is to create a set of modules and classes in a lib file. Here is an example extracted from ours:
# lib/console_extension.rb
module ConsoleExtension
# This module provides methods that are only available in the console
module ConsoleHelpers
def do_someting
puts "doing something"
end
end
# This is a simple class that allows us access to the ConsoleHelpers before
# we get into the console
class ConsoleRunner
include ConsoleExtension::ConsoleHelpers
end
# This is specifically to patch into the startup behavior for the console.
#
# In the console_command.rb file, it does this right before start:
#
# if defined?(console::ExtendCommandBundle)
# console::ExtendCommandBundle.include(Rails::ConsoleMethods)
# end
#
# This is a little tricky. We're defining an included method on this module
# so that the Rails::ConsoleMethods module gets a self.included method.
#
# This causes the Rails::ConsoleMethods to run this code when it's included
# in the console::ExtendCommandBundle at the last step before the console
# starts, instead of during the earlier load_console stage.
module ConsoleMethods
def included(_klass)
ConsoleExtension::ConsoleRunner.new.do_someting
end
end
end
The next step is to add the following into your application.rb file:
module MyApp
class Application < Rails::Application
...
console do
require 'console_extension' # lib/console_extension.rb
Rails::ConsoleMethods.send :include, ConsoleExtension::ConsoleHelpers
Rails::ConsoleMethods.send :extend, ConsoleExtension::ConsoleMethods
end
end
end
Now, every time you run rails console, it will do something:
If you're just looking to run something once every time the console starts, this is more complicated than it needs to be. Instead, you can just use the console() method in MyApp::Application and it will run whatever code you want as part of the load_console step.
module MyApp
class Application < Rails::Application
...
console do
puts "do something"
end
end
end
One issue we had with this was that it runs the code before it prints out the environment, so if you're doing any printing or interaction it feels a bit weird:
You may not be as picky as we are though. Do whatever makes you and your team the happiest.
I dont know if its a good practice, but you can check if server is running on Console, like Aditya awnsered
if defined?(Rails::Console)
MyClass.some_method()
end
Note that this won't work during Rails initialization when running Spring like Swartz said.
I would try creating a Rake task for it and invoke it with after_initialize:
config.after_initialize do
IndividualProject::Application.load_tasks
Rake::Task[ 'foo:bar' ].invoke
end
So I was searching online for a solution, but there seems to be scarce information about testing initializers created in Rails.
At the moment, I've written a pretty large API call-n-store in my config/initializers/retrieve_users.rb file. It makes an API request, parses the JSON, and then stores the data as users. Being pretty substantial, I've yet to figure out the cleanest way to test it. Since I need to retrieve the users before any functions are run, I don't believe I can move this script anywhere else (although other suggestions would be welcomed). I have a couple questions about this:
Do I have to wrap this in a function/class to test my code?
Where would I put my spec file?
How would I format it RSpec-style?
Thanks!
I just gave this a shot and have passing rspec tests, I am going to further refactor my code and in the end it will be shorter, but here is a snapshot of what I did, using this link as a guide:
The gist of it is this: make a class in your initializer file with the functions you want, then write tests on the functions in the class.
config/initializers/stripe_event.rb
StripeEvent.configure do |events|
events.subscribe 'charge.dispute.created' do |event|
StripeEventsResponder.charge_dispute_created(event)
end
end
class StripeEventsResponder
def self.charge_dispute_created(event)
StripeMailer.admin_dispute_created(event.data.object).deliver
end
end
spec/config/initializers/stripe_events_spec.rb
require 'spec_helper'
describe StripeEventsResponder do
before { StripeMock.start }
after { StripeMock.stop }
after { ActionMailer::Base.deliveries.clear }
describe '#charge_dispute_created' do
it "sends one email" do
event = StripeMock.mock_webhook_event('charge.dispute.created')
StripeEventsResponder.charge_dispute_created(event)
expect(ActionMailer::Base.deliveries.count).to eq(1)
end
it "sends the email to the admin" do
event = StripeMock.mock_webhook_event('charge.dispute.created')
StripeEventsResponder.charge_dispute_created(event)
expect(ActionMailer::Base.deliveries.last.to).to eq(["admin#example.com"])
end
end
end
Seems like this would be tricky, since the initializers are invoked when loading the environment, which happens before the examples are run.
I think you've already figured out the most important step: move the code into another unit where it can be tested outside of the Rails initialization process. This could be a class or a module. Mock out the network dependencies using webmock.
Where should this code reside? See this (now quite ancient) answer by no less than Yehuda Katz himself. Put the spec file in the corresponding part of the tree; i.e. if the unit ends up in lib/my_class.rb, the spec goes in spec/lib/my_class_spec.rb. You may need to modify spec/rails_helper to load the spec file.
I'm using RSpec and I want to get result (passed or not), class name, test name (or description) and error message (if present) of each test after once it finished.
So here is a pretty simple code
describe MyClass do
after :each do
#how do I know a test passed, its name, class name and error message?
end
it "should be true" do
5.should be 5
end
it "should be false" do
5.should be 6
end
end
Your suggestions?
There are a few formatters for test output that can show you which tests passed/failed/pending, and it sounds like you are interested in the one called documentation.
In order to make this formatter available for all your rspec files, create a file called .rspec with content:
--color # I just like this one for more easily read output
--formatter documentation
This means that when you run a test suite like you have above you will see:
MyClass
should be true
should be false (Failed - 1)
# Individual output for each error would show up down here
Reference: https://www.relishapp.com/rspec/rspec-core/v/2-11/docs/formatters/custom-formatters
You can get extra information from formatters, but since after hooks are potential points of failure, they don't expose failure information themselves.
See https://www.relishapp.com/rspec/rspec-core/v/2-11/docs/formatters/custom-formatters and https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/formatters/base_formatter.rb
I've got some Rspec tests that I'm using to test a module. The module is used by a number of models in my Rails app. In order to keep testing time down to a minimum, I want to avoid loading Rails when testing my module.
The problem is that there are a few methods that contain rescues and those rescues mention the Rails debugger. When Rspec sees the capital 'r' in Rails, it has a bad time because it considers it an uninitialized constant.
I was hoping that if I wrapped up the logging info into its own method, I could avoid the problem. Here's what I came up with. But if I go down this road, I would need to stub either the rescue code or or the log_info method. Is that right? How would I do that? And which one should I stub?
def download_robots_file(page)
file = Net::HTTP.get(URI("#{page}robots.txt"))
rescue StandardError => ex
log_info('robot_file', ex)
end
....
private
def log_info(problem, exception_issue)
Rails.logger.debug("Couldn't download #{problem}: " + exception_issue.inspect)
end
You can add to stub chain method that you want to stub.
Rails.stub_chain(:logger).and_return(mocked_logger_instance)
Un-stub in the end with:
Rails.unstub(:logger)
All credits go to mister on following link How to rspec mock ruby rails logger class
Below you can see that I'm calling File.open only once but rspec is telling me it received it 3 times.
def self.import_file(filename)
current_file = filename.split('/').last
destroy_all(["filename = ?",current_file])
unpack_format = "A#{INPUT_FILE_FORMAT.map{|element| element[1]}.join("A")}"
debugger
File.open(filename, 'r').each do |line|
hash_created = create_hash(line, unpack_format).merge({:filename=>current_file})
create(hash_created)
end
end
it "should delete previous records with the same filename" do
Payrec.should_receive(:destroy_all).with(["filename = ?", "testfile.txt"])
File.should_receive(:open).and_return([#file_line])
Payrec.import_file "testfile.txt"
end
The output is
<File (class)> expected :open with (any args) once, but received it 3 times
Everybody and his dog calls File.open. I could imagine a ton of reasons why it would called: RSpec reading its config file, Rails reading its config file(s), Cucumber reading its config file, the debugger creating a temporary file, something else creating a temporary file and so on.
You should check who is calling File.open, where that call happens, what the arguments are and why that is happening.
But, this is something you will just have to deal with, when setting expectations on core methods.
Imagine, for example, you are running your specs on Rubinius. In Rubinius, the compiler is written in Ruby. It doesn't currently cache its compiled results, but it is certainly conceivable that it could cache them, and then it would naturally use File.open. Bam! Now your specs randomly break depending on whether you hit the JIT threshold or not.
Or, even worse: all of Rubinius uses arrays and symbols extensively to implement just about everything. Try setting some expectations on those!
It's far from perfect, but as a quick hack for finding out what these File.open calls are actually doing, monkey patch this in at the top of your main script:
class File
class << self
alias_method :old_open, :open
def open(*args, &block)
p args
block ? block.call(old_open(*args)) : old_open(*args)
end
end
end
Whenever File.open is called, the arguments will be put to screen.