I have an application and I want to test if I get correct
messages from my logger.
A short example (you may switch between log4r and logger):
gem 'minitest'
require 'minitest/autorun'
require 'log4r'
#~ require 'logger'
class Testlog < MiniTest::Test
def setup
if defined? Log4r
#log = Log4r::Logger.new('log')
#log.outputters << Log4r::StdoutOutputter.new('stdout', :level => Log4r::INFO)
else
#log = Logger.new(STDOUT)
#log.level = Logger::INFO
end
end
def test_silent
assert_silent{ #log.debug("hello world") }
assert_output(nil,nil){ #log.debug("Hello World") }
end
def test_output
#~ refute_silent{ #log.INFO("Hello") }#-> NoMethodError: undefined method `refute_silent'
assert_output("INFO log: Hello World\n",''){ #log.info("Hello World") }
end
end
But I get:
1) Failure:
Testlog#test_output [minitest_log4r.rb:27]:
In stdout.
Expected: "INFO log: Hello World\n"
Actual: ""
On my output screen I see the message.
I have similar results with Log4r::StderrOutputter and Log4r::Outputter.stdout.
So it seems it is send to the output screen, but it is not catched by minitest in STDOUT or STDERR.
Before I start to write a minitest-log4r-Gem:
Is there a possibility to test logger-output in minitest?
If not:
Any recommendations how to implement a minitest-log4r-Gem?
Examples what I could imagine:
define new outputter for minitest (Log4r::MinitestOutputter)
Mock the logger.
new assertions (add the new outputter as parameter?):
assert_message('INFO log: Hello World'){ #log.info("Hello World") }
assert_messages(:info => 1, :debug => 2){ #log.info("Hello World") } to count messages.
assert_info_messages('Hello World'){ #log.info("Hello World") }
assert_debug_messages('Hello World'){ #log.info("Hello World") }
You can set up a pipe, pass the writer from the pipe to the logger, and then use the reader from the pipe to test your assertions.
http://ruby-doc.org/core-2.1.0/IO.html#method-c-pipe
Something like:
require 'logger'
r, w = IO.pipe
log = Logger.new(w)
log.info "testing info log message"
output = r.gets
puts "Test passed: #{!!(/testing/ =~ output)}"
In meantime I created a minitest-logger-Gem
A code example how to use it:
require 'log4r'
require 'minitest-logger'
class Test_log4r < MiniTest::Test
def setup
#log = Log4r::Logger.new('log')
#log.level = Log4r::INFO
end
def test_output_1
assert_log(" INFO log: Hello World\n"){ #log.info("Hello World") }
end
def test_output_regex
assert_log(/Hello World/){ #log.info("Hello World") }
end
def test_silent
assert_silent_log(){
#log.debug("Hello World")
#~ #log.info("Hello World") #uncomment this to see a failure
}
refute_silent_log(){
#log.warn("Hello World") #comment this to see a failure
}
end
end
During the test a temporary outputter is added to the logger #log. After the test the outputter is removed again.
The gem supports log4r and logger.
#Puhlze answer is good. Just for non-blocking, check before hand if there is input available:
if IO.select([r], [], [], 0.01).nil?
Suppose we have this code here on a file called logger.rb:
require 'logger'
class Framework
attr_reader :logger
def initialize
#logger = Logger.new("/tmp/minitest.log")
end
end
class Custom
def initialize(framework)
#framework = framework
end
def error!
raise StandardError, 'Forced error'
rescue StandardError => e
#framework.logger.error "Error: #{e}"
end
end
And we need to test the logger error messages. We can use a stub method and a StringIO object:
require 'minitest'
require 'minitest/autorun'
require_relative 'logger.rb'
class LoggerTest < MiniTest::Test
def test_logger
framework = Framework.new
custom = Custom.new(framework)
io = StringIO.new
framework.stub :logger, Logger.new(io) do
custom.error!
assert_match(/Forced error/, io.string)
end
end
end
This way we don't need to override the framework logger, just stub it.
Related
When I run rails c, I can call the following class and the method works:
test = SlackService::BoardGameNotifier
test.create_alert("test")
>>method works
I'm trying to set this up in rspec like this:
require 'spec_helper'
require 'slack-notifier'
RSpec.describe SlackService::BoardGameNotifier do
describe '#notify' do
#notifier = SlackService::BoardGameNotifier
it 'pings Slack' do
error = nil
message = "test"
expect(notifier).to receive(:ping).with(message)
notifier.send_message()
end
end
end
But I keep getting the error:
NameError:
uninitialized constant SlackService
Does this have to do with how I set up the module?
My current setup:
slack_service/board_game_notifier.rb
module SlackService
class BoardGameNotifier < BaseNotifier
WEBHOOK_URL = Rails.configuration.x.slack.url
DEFAULT_OPTIONS = {
channel: "board-games-channel",
text: "board games alert",
username: "bot",
}
def create_alert(message)
message #testing
end
end
end
slack_service/base_notifier.rb
module SlackService
class BaseNotifier
include Singleton
def initialize
webhook_url = self.class::WEBHOOK_URL
options = self.class::DEFAULT_OPTIONS
#notifier = Slack::Notifier.new(webhook_url, options)
end
def self.send_message
message = instance.create_alert("test")
instance.notify(message)
end
def notify(message)
#notifier.post blocks: message
end
end
end
Add this to your spec_helper.rb
# spec_helper.rb
ENV["RAILS_ENV"] ||= "test"
require File.expand_path("../config/environment", __dir__)
When running RSpec, Rails doesn't automatically boot up, and therefore doesn't automatically load all the libraries.
Also, I'd suggest creating a .rspec in your app's root folder with the following lines so that spec_helper is automatically loaded for all your RSpec tests:
# .rspec
--format documentation
--color
--require spec_helper
I would use the described_class from Rspec
require 'spec_helper'
require 'slack-notifier'
RSpec.describe ::SlackService::BoardGameNotifier do
describe '#notify' do
it 'pings Slack' do
error = nil
message = "test"
expect(described_class).to receive(:ping).with(message)
notifier.send_message()
end
end
end
I want to mock a class with Ruby.
How do I write a method that will take care of the boilerplate code?
The following code:
module Mailgun
end
module Acani
def self.mock_mailgun(mock)
temp = Mailgun
const_set(:Mailgun, mock)
p Mailgun
yield
ensure
const_set(:Mailgun, temp)
end
end
Acani.mock_mailgun('mock') { p Mailgun }
prints:
"mock"
Mailgun
What's going on here? Why is Mailgun its original value inside the block? Does this have to do with Ruby bindings?
Ruby version: 2.1.1p76
Try putting Object. before each const_set.
The code in the question is simplified. Here is the pertinent code:
test/test_helper.rb
require 'minitest/autorun'
module Acani
def self.const_mock(const, mock)
temp = const_get(const)
const_set_silent(const, mock)
yield
ensure
const_set_silent(const, temp)
end
private
def self.const_set_silent(const, value)
temp = $VERBOSE
$VERBOSE = nil
Object.const_set(const, value)
ensure
$VERBOSE = temp
end
end
test/web_test.rb
require 'test_helper'
require 'rack/test'
require_relative '../web'
class AppTest < MiniTest::Test
include Rack::Test::Methods
def app
Sinatra::Application
end
def test_password_reset
post '/users', {email: 'user1#gmail.com', password: 'password1'}
mailgun_mock = MiniTest::Mock.new
mailgun_mock.expect(:send, 200, [Hash])
Acani.const_mock(:Mailgun, mailgun_mock) do
post '/password_resets', {email: 'user1#gmail.com'}
end
mailgun_mock.verify
assert_equal 201, last_response.status
end
end
Currently I'm using puts, but I'm sure that's not the correct answer. How do I correctly setup a logger, inside my gem, to output my internal logging instead of puts?
The most flexible approach for users of your gem is to let them provide a logger rather than setting it up inside the gem. At its simplest this could be
class MyGem
class << self
attr_accessor :logger
end
end
You then use MyGem.logger.info "hello" to log messages from your gem (you might want to wrap it in a utility method that tests whether a logger is set at all)
Users of your gem can then control where messages get logged to (a file, syslog, stdout, etc...)
You can keep the logger in your top-level module. Allow user's to set their own logger but provide a reasonable default for those who don't care to deal with logging. For e.g.
module MyGem
class << self
attr_writer :logger
def logger
#logger ||= Logger.new($stdout).tap do |log|
log.progname = self.name
end
end
end
end
Then, anywhere within your gem code you can access the logger. For e.g.
class MyGem::SomeClass
def some_method
# ...
MyGem.logger.info 'some info'
end
end
References:
Using the Ruby Logger
Logger
A little example:
gem 'log4r'
require 'log4r'
class MyClass
def initialize(name)
#log = Log4r::Logger.new(name)
#Add outputter
#~ log.outputters << Log4r::FileOutputter.new('log_file', :filename => 'mini_example.log', :level => Log4r::ALL )
log.outputters << Log4r::StdoutOutputter.new('log_stdout') #, :level => Log4r::WARN )
#~ log.outputters << Log4r::StderrOutputter.new('log_stderr', :level => Log4r::ERROR)
#log.level = Log4r::INFO
#log.info("Creation")
#~ #log.level = Log4r::OFF
end
attr_reader :log
def myfunction(*par)
#log.debug("myfunction is called")
#log.warn("myfunction: No parameter") if par.empty?
end
end
x = MyClass.new('x')
x.myfunction
y = MyClass.new('y')
y.myfunction
y.myfunction(:a)
y.log.level = Log4r::DEBUG
y.myfunction(:a)
During initialization you create a Logger (#log). In your methods you call the logger.
With #log.level= (or MyClass#log.level=) you can influence, which messages are used.
You can use different outputters (in my example I log to STDOUT). You can also mix outputters (e.g. STDOUT with warnings, each data (including DEBUG) to a file...)
I think the easiest approach is to use it this way
Rails.logger.info "hello"
I've completed the First Gem section of this tutorial. However, in the Requiring More Gems section when I tried modifying my hola.rb as such:
class Hola
def self.hi(language = :english)
translator = Translator.new(language)
translator.hi
end
end
require 'hola/translator'
and added this translator.rb file:
class Hola::Translator
def initialize(language)
#language = language
end
def hi
case #language
when :spanish
"hola mundo"
else
"hello world"
end
end
end
If I typed in these commands in IRB, I should get identical outputs:
% irb -Ilib -rhola
irb(main):001:0> Hola.hi(:english)
=> "hello world"
irb(main):002:0> Hola.hi(:spanish)
=> "hola mundo"
However, I'm not getting proper results. I tried modifying my gemspec and reinstalling the gem, but no success. What do you think I did wrong in this case?
stanley#ubuntu:~/Github/webdev_class/ruby/hola_stanley/lib$ irb -Ilib -rhola
irb(main):001:0> Hola.hi(:english)
=> "hello world"
irb(main):002:0> Hola.hi(:spanish)
=> "hello world"
irb(main):003:0> quit
I don't see, what's wrong, but are you sure there was no type and you requested the file you expected?
I would modify the code a bit:
class Hola::Translator
def initialize(language, exceptions = false)
#language = language
#exceptions = exceptions
end
def hi
case #language
when :spanish
"hola mundo"
when :english
"hello world"
else
raise ArgumentError, "Unsupported language #{#language.inspect}" if #exceptions
"hello world"
end
end
end
You support two languages: Spanish and English. Other languages are detected and you can select, if you get an error or an alternative (english) text.
You can start it with:
class Hola
def self.hi(language = :english)
translator = Translator.new(language, true) ## <-- Modified
translator.hi
end
end
require 'hola/translator'
One advantage of the expection: You can see, which file really calls the exception.
Some other remarks:
I would recommend to define Hola as a module, not a class.
Instead the raise you could define a logger and report an error.
Example:
require 'log4r'
module Hola
LOG = Log4r::Logger.new('Hola')
class Translator
def initialize(language)
#language = language
end
def hi
case #language
when :spanish
"hola mundo"
when :english
"hello world"
else
LOG.error("Unsupported language #{#language.inspect}")
"hello world"
end
end
end
def self.hi(language = :english)
translator = Translator.new(language)
translator.hi
end
end #module Hola
p Hola.hi
p Hola.hi(:english)
p Hola.hi(:spanish)
p Hola.hi(:german)
puts "Activate Hola-warnings"
Hola::LOG.outputters << Log4r::StdoutOutputter.new('stdout')
p Hola.hi
p Hola.hi(:english)
p Hola.hi(:spanish)
p Hola.hi(:german)
After activating the logger, you are informed, if you have a type in :spanish (that's the error I would expect).
I would like start a long poll request from javascript which is fine and i expect my ruby prog to stream multiple body sections to the javascript. Why doesn the following (pseudo)code work?
require 'rubygems'
require 'sinatra/async'
require 'eventmachine'
require 'thin'
require 'json'
class Test < Sinatra:Base
register Sinatra::Async
aget '/process' do
for c in 1..10
body {
{ :data => [ "this is part #{c}" ] }.to_json
end
end
end
run!
end
Maybe i misunderstood what long polling and async is supposed to do, but my expectation is that i get multiple bodies sent back to the client ? Do i need to use eventmachine or something?
thanks
require 'rubygems'
require 'sinatra/async'
require 'thin'
require 'json'
class Test < Sinatra::Base
register Sinatra::Async
class JSONStream
include EventMachine::Deferrable
def stream(object)
#block.call object.to_json + "\n"
end
def each(&block)
#block = block
end
end
aget '/process' do
puts 'ok'
out = JSONStream.new
body out
EM.next_tick do
c = 0
timer = EM.add_periodic_timer(0.3) do
c += 1
out.stream :data => ["this is part #{c}"]
if c == 100
timer.cancel
out.succeed
end
end
end
end
run!
end
See also: http://confreaks.net/videos/564-scotlandruby2011-real-time-rack
It appears in the example below that you need an EventMachine event to trigger the sending of the multiple bodies. Also see this previous answer as well.
require 'sinatra/async'
class AsyncTest < Sinatra::Base
register Sinatra::Async
aget '/' do
body "hello async"
end
aget '/delay/:n' do |n|
EM.add_timer(n.to_i) { body { "delayed for #{n} seconds" } }
end
end