# An RSpec formatter for generating results in JUnit format class JUnit < RSpec::Core::Formatters::BaseFormatter RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending, :dump_summary def initialize(output) super @test_suite_results = {} @builder = Builder::XmlMarkup.new :indent => 2 end def example_passed(example_notification) add_to_test_suite_results example_notification end def example_failed(example_notification) add_to_test_suite_results example_notification end def example_pending(example_notification) add_to_test_suite_results example_notification end def dump_summary(summary) build_results(summary.duration, summary.examples.size, summary.failed_examples.size, summary.pending_examples.size) @output.puts @builder.target! end protected def add_to_test_suite_results(example_notification) suite_name = JUnit.root_group_name_for example_notification @test_suite_results[suite_name] = [] unless @test_suite_results.keys.include? suite_name @test_suite_results[suite_name] << example_notification.example end def failure_details_for(example) exception = example.exception exception_backtrace = exception.backtrace formatted_backtrace = '' if exception_backtrace # may be nil formatted_backtrace = "\n#{RSpec::Core::BacktraceFormatter.new.format_backtrace exception_backtrace}" end exception.nil? ? '' : "#{exception.message}#{formatted_backtrace}" end # utility methods def self.count_in_suite_of_type(suite, test_case_result_type) suite.select { |example| example.metadata[:execution_result].status == test_case_result_type }.size end def self.root_group_name_for(example_notification) example_notification.example.metadata[:example_group][:description] end # methods to build the xml for test suites and individual tests def build_results(duration, example_count, failure_count, pending_count) @builder.instruct! :xml, :version => "1.0", :encoding => "UTF-8" @builder.testsuites( :errors => 0, :failures => failure_count, :skipped => pending_count, :tests => example_count, :time => duration, :timestamp => Time.now.iso8601) do build_all_suites end end def build_all_suites @test_suite_results.each do |suite_name, tests| build_test_suite(suite_name, tests) end end def build_test_suite(suite_name, tests) failure_count = JUnit.count_in_suite_of_type(tests, :failed) skipped_count = JUnit.count_in_suite_of_type(tests, :pending) @builder.testsuite( :location => tests.first.example_group.location, :name => suite_name, :tests => tests.size, :errors => 0, :failures => failure_count, :skipped => skipped_count) do @builder.properties build_all_tests tests end end def build_all_tests(tests) tests.each do |test| build_test test end end def build_test(test) test_name = test.metadata[:full_description] execution_time = test.metadata[:execution_result].run_time test_status = test.metadata[:execution_result].status location = test.location @builder.testcase(:name => test_name, :time => execution_time, :location => location) do case test_status when :pending @builder.skipped when :failed build_failed_test test end end end def build_failed_test(test) failure_message = "failed #{test.metadata[:full_description]}" @builder.failure(:message => failure_message, :type => 'failed') do @builder.cdata!(failure_details_for(test)) end end end