Custom RSpec formatter to display passed test and result of except - ruby

Is there a way to create a custom formatter where the passed test details with a list of except is showed?
A bit of a background for this question: we are trying to migrate to RSpec for our hardware integration and system test. The results should be pushed to CouchDB. What I am trying to achieve is a reporter that could generate a similar YAML output like the following snippet:
{
"_id": "0006b6f0-c1bd-0135-1a98-455c37fe87f1",
"_rev": "1-9c9786b4b4681ee8493f182d4fc56ef9",
"sha1_repo": "68bb327b540097c10683830f0d82acbe54a47f03",
"steps": [
{
"result": "pass",
"description": "Time for Routing expect OK: 126 micro seconds (DLC and Data also OK)"
},
{
"result": "pass",
"description": "Time for Routing expect OK: 146 micro seconds (DLC and Data also OK)"
},
{
"result": "pass",
"description": "Time for Routing expect OK: 162 micro seconds (DLC and Data also OK)"
}
],
"time_start": "1513119108000",
"time_end": "1513119108000",
"result": "pass",
"testcase_title": "Komfort_TSG_HBFS_03_to_Komfort2_TSG_HBFS_03",
"testcase_id": "TC_1zu1_BAF_Komfort_TSG_HBFS_03_to_Komfort2_TSG_HBFS_03",
"hierarchy": [
"Hardware Integration Test",
"1 - Routing",
"1.1 Normal Routing",
"1zu1_BAF_TestCases",
"CAN_to_CAN"
]
}
With failed test there is no problem to achieve this, but we need also the results from passed test in order to be able to create long term statistics.
I can override the passed event of RSPec but the example object delivers only the description and no more info.
class EliteReporter
RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
def example_passed(passed)
#output.printf "pass \n #{passed.example.description}"
end
end
Thank you in advance for any help.

Finally with the help of my colleague and thanks of the Tip from RSPec Emailing list I could do this.
I have created a Recorder class that collects the test results, than override the Expect methode. This way in the custom formatter I can collect all the passed results:
class ExpectWrapper
def initialize(_expect, _recorder, _description)
#expect = _expect
#recorder = _recorder
#description = _description
end
def to(matcher, failure_message=nil)
begin
expect_ret = #expect.to(matcher, failure_message) # test
# for tests that aggregate failures
if expect_ret.instance_of?(TrueClass)
#recorder.record(matcher.actual, matcher.description, #description)
else
#recorder.record_error(matcher.actual, matcher.description, failure_message, #description)
end
expect_ret
rescue RSpec::Expectations::ExpectationNotMetError => e
# for test that do not aggregate failures
#recorder.record_error(matcher.actual, matcher.description, failure_message, #description)
raise e
end
end
end
class Recorder
def self.start
##data = []
return Recorder.new
end
def record(expect, data, description)
##data << { :pass => true, :expect => expect, :value => data, :description => description }
self
end
def record_error(expect, data, failure_message, description)
##data << { :pass => false, :expect => expect, :value => data, :message => failure_message, :description => description }
self
end
def self.data
##data
end
def expect(object, value, description = "")
return ExpectWrapper.new(object.expect(value), self, description)
end
end
The custom formatter would look the following, is just an example, the data could be than put to JSON and pushed to Couch:
class EliteVerboseFormatter
RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
def initialize(output)
#output = output
end
def example_passed(notification)
#output.puts( format_output(notification.example, Recorder) )
end
def get_test_name( group, description)
"#{group.example.example_group}/#{description}".gsub('RSpec::ExampleGroups::','')
end
def format_output( example, recorder )
test_case = get_test_name( example.example_group, example.description)
str = "**********TEST: #{test_case} ************\n"
recorder.data.each do |d|
str += sprintf("%s: ---> expected '%-10s' to '%-20s' DESC: %s \n", d[:pass] ? 'PASS' : 'FAIL', d[:expect], d[:value], d[:description])
end
str
end
def example_failed(notification)
#output.puts(format_output( notification.example, Recorder))
exception = notification.exception
message_lines = notification.fully_formatted_lines(nil, RSpec::Core::Notifications::NullColorizer)
exception_details = if exception
{
# drop 2 removes the description (regardless of newlines) and leading blank line
:message => message_lines.drop(2).join("\n"),
:backtrace => notification.formatted_backtrace.join("\n"),
}
end
#output.puts RSpec::Core::Formatters::ConsoleCodes.wrap(exception_details[:message], :failure)
end
end

I think you can read the Module: RSpec::Core::Formatters
you might find something helpful.
P.S. I have used Cucumber for many times, and I once wanted to custom cucumber formatter to display every step's details no matter it failed or passed. I finally got the solution by reading cucumber core documents.so I think maybe rspec core document can help you to find the solution.

I find that I cannot put the code in comment, so I put it here.
edit your code as below:
class EliteReporter
RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
def example_passed(example)
example_failed(example)
end
end
I hope it can be helpful: )

Related

Parse JSON like syntax to ruby object

Simple parser which turned out to be much harder than i thought. I need a string parser to convert nested fields to ruby object. In my case api response will only return desired fields.
Given
Parser.parse "album{name, photo{name, picture, tags}}, post{id}"
Desired output or similar
{album: [:name, photo: [:name, :picture, :tags]], post: [:id]}
Any thoughts?
Wrote my own solution
module Parser
extend self
def parse str
parse_list(str).map do |i|
extract_item_fields i
end
end
def extract_item_fields item
field_name, fields_str = item.scan(/(.+?){(.+)}/).flatten
if field_name.nil?
item
else
fields = parse_list fields_str
result = fields.map { |field| extract_item_fields(field) }
{ field_name => result }
end
end
def parse_list list
return list if list.nil?
list.concat(',').scan(/([^,{}]+({.+?})?),/).map(&:first).map(&:strip)
end
end
str = 'album{name, photo{name, picture, tags}}, post{id}'
puts Parser.parse(str).inspect
# => [{"album"=>["name", {"photo"=>["name", "picture", "tags"]}]}, {"post"=>["id"]}]

Test failing - one class method to call another ("expected: 1 time with arguments, received 0 times")

My problem:
I'm trying to stub a class method that returns an instance of that class, but I'm getting the following error for the test entitled "creates an instance with CSV data":
Failures:
1) QuestionData.load_questions creates an instance with CSV data
Failure/Error: expect(question_data_class).to receive(:new).with(data).and_return(question_data_instance)
(QuestionData (class)).new([{:time_limit=>10, :text=>"Who was the legendary Benedictine monk who invented champagne?", :correct_...the world?", :correct_answer=>"Lake Superior", :option_2=>"Lake Victoria", :option_3=>"Lake Huron"}])
expected: 1 time with arguments: ([{:time_limit=>10, :text=>"Who was the legendary Benedictine monk who invented champagne?", :correct_...the world?", :correct_answer=>"Lake Superior", :option_2=>"Lake Victoria", :option_3=>"Lake Huron"}])
received: 0 times
The context:
The code (shown below) works - QuestionData.load_questions loads data from a CSV file and calls QuestionData.new with the data as an argument. My test for the .load_questions method however, is giving the above error. When it's called, the double of the QuestionData class isn't receiving the stub of .new with the data double.
I've tried researching how to test stubs that return another stub or an instance, but can't seem to find a relevant answer.
I'd really appreciate any help or advice, thanks very much in advance!
The code:
require "csv"
class QuestionData
attr_reader :questions
def initialize(questions)
#questions = questions
end
def self.load_questions(file = './app/lib/question_list.csv', questions = [])
self.parse_csv(file, questions)
self.new(questions)
end
def self.parse_csv(file, questions)
CSV.foreach(file) do |row|
time_limit, text, correct_answer, option_2, option_3 = row[0],
row[1], row[2], row[3], row[4]
questions << { time_limit: time_limit, text: text,
correct_answer: correct_answer, option_2: option_2, option_3: option_3
}
end
end
end
The test file:
require './app/models/question_data'
describe QuestionData do
subject(:question_data_instance) { described_class.new(data) }
let(:question_data_class) { described_class }
let(:CSV) { double(:CSV, foreach: nil) }
let(:questions) { [] }
let(:file) { double(:file) }
let(:data) do
[{
time_limit: 10,
text: "Who was the legendary Benedictine monk who invented champagne?",
correct_answer: "Dom Perignon",
option_2: "Ansgar",
option_3: "Willibrord"
},
{
time_limit: 12,
text: "Name the largest freshwater lake in the world?",
correct_answer: "Lake Superior",
option_2: "Lake Victoria",
option_3: "Lake Huron"
}]
end
describe '#questions' do
it "has an array of question data from CSV" do
expect(question_data_instance.questions).to eq(data)
end
end
describe '.parse_csv' do
it "parses CSV data into hash data" do
expect(CSV).to receive(:foreach).with(file)
question_data_class.parse_csv(file, questions)
end
end
describe '.load_questions' do
it "calls '.parse_csv' method" do
expect(question_data_class).to receive(:parse_csv).with(file, questions)
question_data_class.load_questions(file, questions)
end
it "creates an instance with CSV data" do
allow(question_data_class).to receive(:load_questions).with(file, questions).and_return(question_data_instance)
allow(question_data_class).to receive(:new).with(data).and_return(question_data_instance)
expect(question_data_class).to receive(:new).with(data).and_return(question_data_instance)
question_data_class.load_questions(file, questions)
end
end
describe '.new' do
it "creates a new instance with CSV data" do
expect(question_data_class).to receive(:new).with(data).and_return(question_data_instance)
question_data_class.new(data)
end
end
end
The thing is that you are stubbing the call on:
allow(question_data_class).to receive(:load_questions).with(file)
If you still want that the call executes you need to add a:
and_call_original
Therefore the original method will be executed and your code will call the new method on the original block.
But the thing is that you don't need to stub the class you just need to change the stubs because you are calling the method on a double, and it will try to execute it in a class, so you might need to change your second test to:
describe '.load_questions' do
it "creates an instance containing CSV data" do
expect(described_class).to receive(:new).with(data).and_return(question_data_instance)
described_class.load_questions(file)
end
end

How to stub/mock multiple options depending on user input with Rspec 3.4

I am completely new to Rspec, and it's my first time trying to test outside of the rails framework. I am simply trying to understand how I can possibly mock behavior of my app when the implementation is pretty complex.
I want to be able to mimic the behavior of calling customize_gender inputting a choice and checking that when 1 is entered the result is 'Male', when 2 is entered the result is 'Female', etc.
I also want to be able to check if the instance variable of #gender was correctly set, which is why I added the attr_reader :gender in the first place. I have been trying a few things, but I guess I do not understand how mocks in general work to be able to find a solution. I have looked at similar questions but they do not seem to work for my scenario. Any insight is greatly appreciated!
Main file (person.rb)
class Person
attr_reader :gender
GENDER = { male: 'Male', female: 'Female', other: 'Other'}
def initialize
puts customize_gender
end
def customize_gender
display_hash_option GENDER, 'What is your gender? '
choice = gets.chomp.to_i
#gender =
case choice
when 1
GENDER[:male]
when 2
GENDER[:female]
when 3
print 'Enter your preferred gender: '
gets.chomp.downcase
else
puts 'Error: Person -> customize_gender()'
end
end
private
def display_hash_option(hash, saying = '')
print saying
hash.each_with_index { |(key, _value), index| print "#{index.next}) #{key} " }
end
end
Rspec File (spec/person_spec.rb)
require_relative "../person"
describe Person do
let(:person) { Person.new }
allow(Person).to receive(:gets).and_return(1,2,3)
person.customize_gender
expect(person.gender).to eq 'Male'
# allow(person).to receive(:customize_gender).and_return('Male')
# expect(Person).to receive(:puts).with('What is your gender?')
# allow(Person).to receive(:gets) { 1 }
# expect(person.gender).to eq 'Male'
end
Here's how you could do it, the only thing mocked here is that gets is set to '1' (remember it's a string in this case as gets input is always a string)
RSpec.describe Person do
subject { Person.new }
it 'returns male as gender when male is chosen' do
allow(subject).to receive(:gets).and_return('1')
subject.customize_gender
expect(subject.gender).to eq('Male')
end
end
For when 3 you could use the following.
RSpec.describe Person do
subject { Person.new }
it 'returns other as gender when other has been entered' do
allow(subject).to receive(:gets).and_return('3', 'other')
subject.customize_gender
expect(subject.gender).to eq('other')
end
end

Parse and substitute markup in Ruby

I want to parse a simple string like:
"My name is **NAME**" to "My name is <strong>NAME</strong\>"
Note:
Cannot use any external gems, even though markdown gem might have done the job.
If I understood you correctly it should be quite simple:
text = "My name is **NAME**"
=> "My name is **NAME**"
text = text.gsub(([a-zA-Z\s]*)(\*\*)([a-zA-Z\s]*)(\*\*)/,"\\1<strong>\\3</strong>")
=> "My name is <strong>NAME</strong>"
I've tested it in irb with this command text.gsub(([a-zA-Z\s]*)(\*\*)([a-zA-Z\s]*)(\*\*)/,"\\1<strong>\\3</strong>")
UPDATED
Consider this, if you wish to handle some more cases:
class SurroundMarkup
def initialize(markup_map: {})
#markup_map = markup_map
end
def format(text)
text.tap do |formatted_text|
#markup_map.each do |markup, tag|
formatted_text.gsub!(/#{markup}(?<text>.*?)#{markup}/) do |m|
start_tag(tag) + $~[:text] + stop_tag(tag)
end
end
end
end
private
def start_tag(tag)
"<#{tag}>"
end
def stop_tag(tag)
"</#{tag}>"
end
end
And you can use as follows:
markup_map = {
/\*\*/ => "strong",
/\*/ => "i",
}
s = SurroundMarkup.new(markup_map: markup_map)
s.format("My name is **NAME**") #=> "My name is <strong>NAME</strong>"
s.format("*Ruby* is cool") #=> "<i>Ruby</i> is cool"

Datamapper into String

I want to be able to see the string like the TwitchTV name I have in my database. Here is my current code
get '/watch/:id' do |id|
erb :mystream
#result = Twitchtvst.all( :fields => [:Twitchtv ],
:conditions => { :user_id => "#{id}" }
)
puts #result
end
result in terminal;
#< Twitchtvst:0x007fb48b4d5a98 >
How do I get that into a string (TwitchTV answer in database)
Opppppsss!
Here is the real code sample. Sorry!
get '/livestream' do
erb :livestream
#users_streams = Twitchtvst.all
puts #users_streams
end
If I add .to_s at users_stream it does not work
By adding .to_csv, not exactly a string, but it should show the content:
get '/livestream' do
erb :livestream
#users_streams = Twitchtvst.all
#users_streams.each do |us|
p us.to_csv
end
end
You're getting a Collection of Twitchtvst objects, so you need to convert each to a String:
puts Twitchtvst.all.map(&:to_s).join

Resources