diff --git a/.rspec b/.rspec index 83e16f8..2cfeec5 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,3 @@ --color --require spec_helper +--order rand \ No newline at end of file diff --git a/Gemfile b/Gemfile index 8235868..bf08a2e 100644 --- a/Gemfile +++ b/Gemfile @@ -22,4 +22,5 @@ end group :test do gem 'test-unit' + gem 'database_cleaner' end diff --git a/Gemfile.lock b/Gemfile.lock index 0d29bd9..2f2a066 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -38,6 +38,7 @@ GEM arel (3.0.3) builder (3.0.4) coderay (1.1.0) + database_cleaner (1.5.3) diff-lcs (1.2.5) erubis (2.7.0) generator_spec (0.9.3) @@ -121,6 +122,7 @@ PLATFORMS ruby DEPENDENCIES + database_cleaner generator_spec pry rspec-rails (~> 3.0) diff --git a/spec/dummy/spec/rails_helper.rb b/spec/dummy/spec/rails_helper.rb index 70c46e3..02d4fff 100644 --- a/spec/dummy/spec/rails_helper.rb +++ b/spec/dummy/spec/rails_helper.rb @@ -29,7 +29,7 @@ RSpec.configure do |config| # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. - config.use_transactional_fixtures = true + config.use_transactional_fixtures = false # RSpec Rails can automatically mix in different behaviours to your tests # based on their file location, for example enabling you to call `get` and @@ -51,6 +51,17 @@ RSpec.configure do |config| # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") + config.before(:suite) do + DatabaseCleaner.strategy = :truncation + DatabaseCleaner.clean_with(:truncation) + end + + config.around(:each) do |example| + DatabaseCleaner.cleaning do + example.run + end + end + require 'swagger_rails/rspec/dsl' config.extend SwaggerRails::RSpec::DSL end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index dd86af5..f81a5fa 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -5,6 +5,7 @@ require File.expand_path('../dummy/config/environment', __FILE__) abort("The Rails environment is running in production mode!") if Rails.env.production? require 'spec_helper' require 'rspec/rails' +require 'database_cleaner' # Add additional requires below this line. Rails is not loaded until this point! # Requires supporting ruby files with custom matchers and macros, etc, in @@ -33,7 +34,7 @@ RSpec.configure do |config| # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. - config.use_transactional_fixtures = true + config.use_transactional_fixtures = false # RSpec Rails can automatically mix in different behaviours to your tests # based on their file location, for example enabling you to call `get` and @@ -49,4 +50,15 @@ RSpec.configure do |config| # The different available types are documented in the features, such as in # https://relishapp.com/rspec/rspec-rails/docs config.infer_spec_type_from_file_location! + + config.before(:suite) do + DatabaseCleaner.strategy = :truncation + DatabaseCleaner.clean_with(:truncation) + end + + config.around(:each) do |example| + DatabaseCleaner.cleaning do + example.run + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 913e28a..4e08ab5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +Dir[File.expand_path(File.join(File.dirname(__FILE__), 'support', '**', '*.rb'))].each { |f| require f } + # This file was generated by the `rails generate rspec:install` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause diff --git a/spec/support/formatter_support.rb b/spec/support/formatter_support.rb new file mode 100644 index 0000000..1d96824 --- /dev/null +++ b/spec/support/formatter_support.rb @@ -0,0 +1,340 @@ +# Refer: https://github.com/rspec/rspec-core/blob/cf8dfdc76586a9bb09da0bd03f0da3f2cf8fc580/spec/support/formatter_support.rb + +module FormatterSupport + def run_example_specs_with_formatter(formatter_option, options={}, &block) + output = run_rspec_with_formatter(formatter_option, options.merge(:extra_options => ["spec/rspec/core/resources/formatter_specs.rb"]), &block) + + return output unless options.fetch(:normalize_output, true) + output = normalize_durations(output) + + caller_line = RSpec::Core::Metadata.relative_path(caller.first) + output.lines.reject do |line| + # remove the direct caller as that line is different for the summary output backtraces + line.include?(caller_line) || + + # ignore scirpt/rspec_with_simplecov because we don't usually have it locally but + # do have it on travis + line.include?("script/rspec_with_simplecov") || + + # this line varies a bit depending on how you run the specs (via `rake` vs `rspec`) + line.include?('/exe/rspec:') + end.join + end + + def run_rspec_with_formatter(formatter, options={}) + extra_options = options.fetch(:extra_options) { [] } + + options = RSpec::Core::ConfigurationOptions.new([ + "--format", formatter, "--order", "defined", *extra_options + ]) + + err, out = StringIO.new, StringIO.new + err.set_encoding("utf-8") if err.respond_to?(:set_encoding) + + runner = RSpec::Core::Runner.new(options) + configuration = runner.configuration + configuration.backtrace_formatter.exclusion_patterns << /rspec_with_simplecov/ + configuration.backtrace_formatter.inclusion_patterns = [] + + yield runner if block_given? + + runner.run(err, out) + out.string + end + + def normalize_durations(output) + output.gsub(/(?:\d+ minutes? )?\d+(?:\.\d+)?(s| seconds?)/) do |dur| + suffix = $1 == "s" ? "s" : " seconds" + "n.nnnn#{suffix}" + end + end + + if RUBY_VERSION.to_f < 1.9 + def expected_summary_output_for_example_specs + <<-EOS.gsub(/^\s+\|/, '').chomp + |Pending: (Failures listed here are expected and do not affect your suite's status) + | + | 1) pending spec with no implementation is pending + | # Not yet implemented + | # ./spec/rspec/core/resources/formatter_specs.rb:11 + | + | 2) pending command with block format with content that would fail is pending + | # No reason given + | Failure/Error: expect(1).to eq(2) + | + | expected: 2 + | got: 1 + | + | (compared using ==) + | # ./spec/rspec/core/resources/formatter_specs.rb:18 + | # ./spec/support/formatter_support.rb:39:in `run_rspec_with_formatter' + | # ./spec/support/formatter_support.rb:3:in `run_example_specs_with_formatter' + | # ./spec/support/sandboxing.rb:14 + | # ./spec/support/sandboxing.rb:7 + | + |Failures: + | + | 1) pending command with block format behaves like shared is marked as pending but passes FIXED + | Expected pending 'No reason given' to fail. No Error was raised. + | Shared Example Group: "shared" called from ./spec/rspec/core/resources/formatter_specs.rb:22 + | # ./spec/rspec/core/resources/formatter_specs.rb:4 + | + | 2) failing spec fails + | Failure/Error: expect(1).to eq(2) + | + | expected: 2 + | got: 1 + | + | (compared using ==) + | # ./spec/rspec/core/resources/formatter_specs.rb:33 + | # ./spec/support/formatter_support.rb:39:in `run_rspec_with_formatter' + | # ./spec/support/formatter_support.rb:3:in `run_example_specs_with_formatter' + | # ./spec/support/sandboxing.rb:14 + | # ./spec/support/sandboxing.rb:7 + | + | 3) a failing spec with odd backtraces fails with a backtrace that has no file + | Failure/Error: Unable to find (erb) to read failed line + | + | RuntimeError: + | foo + | # (erb):1 + | + | 4) a failing spec with odd backtraces fails with a backtrace containing an erb file + | Failure/Error: Unable to find /foo.html.erb to read failed line + | + | Exception: + | Exception + | # /foo.html.erb:1:in `
': foo (RuntimeError) + | + | 5) a failing spec with odd backtraces with a `nil` backtrace raises + | Failure/Error: Unable to find matching line from backtrace + | + | RuntimeError: + | boom + | + |Finished in n.nnnn seconds (files took n.nnnn seconds to load) + |8 examples, 5 failures, 2 pending + | + |Failed examples: + | + |rspec ./spec/rspec/core/resources/formatter_specs.rb:4 # pending command with block format behaves like shared is marked as pending but passes + |rspec ./spec/rspec/core/resources/formatter_specs.rb:32 # failing spec fails + |rspec ./spec/rspec/core/resources/formatter_specs.rb:38 # a failing spec with odd backtraces fails with a backtrace that has no file + |rspec ./spec/rspec/core/resources/formatter_specs.rb:44 # a failing spec with odd backtraces fails with a backtrace containing an erb file + |rspec ./spec/rspec/core/resources/formatter_specs.rb:62 # a failing spec with odd backtraces with a `nil` backtrace raises + EOS + end + else + def expected_summary_output_for_example_specs + <<-EOS.gsub(/^\s+\|/, '').chomp + |Pending: (Failures listed here are expected and do not affect your suite's status) + | + | 1) pending spec with no implementation is pending + | # Not yet implemented + | # ./spec/rspec/core/resources/formatter_specs.rb:11 + | + | 2) pending command with block format with content that would fail is pending + | # No reason given + | Failure/Error: expect(1).to eq(2) + | + | expected: 2 + | got: 1 + | + | (compared using ==) + | # ./spec/rspec/core/resources/formatter_specs.rb:18:in `block (3 levels) in ' + | # ./spec/support/formatter_support.rb:39:in `run_rspec_with_formatter' + | # ./spec/support/formatter_support.rb:3:in `run_example_specs_with_formatter' + | # ./spec/support/sandboxing.rb:14:in `block (3 levels) in ' + | # ./spec/support/sandboxing.rb:7:in `block (2 levels) in ' + | + |Failures: + | + | 1) pending command with block format behaves like shared is marked as pending but passes FIXED + | Expected pending 'No reason given' to fail. No Error was raised. + | Shared Example Group: "shared" called from ./spec/rspec/core/resources/formatter_specs.rb:22 + | # ./spec/rspec/core/resources/formatter_specs.rb:4 + | + | 2) failing spec fails + | Failure/Error: expect(1).to eq(2) + | + | expected: 2 + | got: 1 + | + | (compared using ==) + | # ./spec/rspec/core/resources/formatter_specs.rb:33:in `block (2 levels) in ' + | # ./spec/support/formatter_support.rb:39:in `run_rspec_with_formatter' + | # ./spec/support/formatter_support.rb:3:in `run_example_specs_with_formatter' + | # ./spec/support/sandboxing.rb:14:in `block (3 levels) in ' + | # ./spec/support/sandboxing.rb:7:in `block (2 levels) in ' + | + | 3) a failing spec with odd backtraces fails with a backtrace that has no file + | Failure/Error: ERB.new("<%= raise 'foo' %>").result + | + | RuntimeError: + | foo + | # (erb):1:in `
' + | # ./spec/rspec/core/resources/formatter_specs.rb:41:in `block (2 levels) in ' + | # ./spec/support/formatter_support.rb:39:in `run_rspec_with_formatter' + | # ./spec/support/formatter_support.rb:3:in `run_example_specs_with_formatter' + | # ./spec/support/sandboxing.rb:14:in `block (3 levels) in ' + | # ./spec/support/sandboxing.rb:7:in `block (2 levels) in ' + | + | 4) a failing spec with odd backtraces fails with a backtrace containing an erb file + | Failure/Error: Unable to find /foo.html.erb to read failed line + | + | Exception: + | Exception + | # /foo.html.erb:1:in `
': foo (RuntimeError) + | + | 5) a failing spec with odd backtraces with a `nil` backtrace raises + | Failure/Error: Unable to find matching line from backtrace + | + | RuntimeError: + | boom + | + |Finished in n.nnnn seconds (files took n.nnnn seconds to load) + |8 examples, 5 failures, 2 pending + | + |Failed examples: + | + |rspec ./spec/rspec/core/resources/formatter_specs.rb:4 # pending command with block format behaves like shared is marked as pending but passes + |rspec ./spec/rspec/core/resources/formatter_specs.rb:32 # failing spec fails + |rspec ./spec/rspec/core/resources/formatter_specs.rb:38 # a failing spec with odd backtraces fails with a backtrace that has no file + |rspec ./spec/rspec/core/resources/formatter_specs.rb:44 # a failing spec with odd backtraces fails with a backtrace containing an erb file + |rspec ./spec/rspec/core/resources/formatter_specs.rb:62 # a failing spec with odd backtraces with a `nil` backtrace raises + EOS + end + end + + def send_notification type, notification + reporter.notify type, notification + end + + def reporter + @reporter ||= setup_reporter + end + + def setup_reporter(*streams) + config.add_formatter described_class, *streams + @formatter = config.formatters.first + @reporter = config.reporter + end + + def setup_profiler + config.profile_examples = true + reporter.setup_profiler + end + + def formatter_output + @formatter_output ||= StringIO.new + end + + def config + @configuration ||= + begin + config = RSpec::Core::Configuration.new + config.output_stream = formatter_output + config + end + end + + def configure + yield config + end + + def formatter + @formatter ||= + begin + setup_reporter + @formatter + end + end + + def new_example(metadata = {}) + metadata = metadata.dup + result = RSpec::Core::Example::ExecutionResult.new + result.started_at = ::Time.now + result.record_finished(metadata.delete(:status) { :passed }, ::Time.now) + result.exception = Exception.new if result.status == :failed + + instance_double(RSpec::Core::Example, + :description => "Example", + :full_description => "Example", + :example_group => group, + :execution_result => result, + :location => "", + :location_rerun_argument => "", + :metadata => { + :shared_group_inclusion_backtrace => [] + }.merge(metadata) + ) + end + + def examples(n) + Array.new(n) { new_example } + end + + def group + group = class_double "RSpec::Core::ExampleGroup", :description => "Group" + allow(group).to receive(:parent_groups) { [group] } + group + end + + def start_notification(count) + ::RSpec::Core::Notifications::StartNotification.new count + end + + def stop_notification + ::RSpec::Core::Notifications::ExamplesNotification.new reporter + end + + def example_notification(specific_example = new_example) + ::RSpec::Core::Notifications::ExampleNotification.for specific_example + end + + def group_notification group_to_notify = group + ::RSpec::Core::Notifications::GroupNotification.new group_to_notify + end + + def message_notification(message) + ::RSpec::Core::Notifications::MessageNotification.new message + end + + def null_notification + ::RSpec::Core::Notifications::NullNotification + end + + def seed_notification(seed, used = true) + ::RSpec::Core::Notifications::SeedNotification.new seed, used + end + + def failed_examples_notification + ::RSpec::Core::Notifications::ExamplesNotification.new reporter + end + + def summary_notification(duration, examples, failed, pending, time) + ::RSpec::Core::Notifications::SummaryNotification.new duration, examples, failed, pending, time + end + + def profile_notification(duration, examples, number) + ::RSpec::Core::Notifications::ProfileNotification.new duration, examples, number, reporter.instance_variable_get('@profiler').example_groups + end + +end + +if RSpec::Support::RubyFeatures.module_prepends_supported? + module RSpec::Core + class Reporter + module EnforceRSpecNotificationsListComplete + def notify(event, *args) + return super if caller_locations(1, 1).first.label =~ /publish/ + return super if RSPEC_NOTIFICATIONS.include?(event) + + raise "#{event.inspect} must be added to `RSPEC_NOTIFICATIONS`" + end + end + + prepend EnforceRSpecNotificationsListComplete + end + end +end \ No newline at end of file diff --git a/spec/swagger_rails/rspec/formatter_spec.rb b/spec/swagger_rails/rspec/formatter_spec.rb new file mode 100644 index 0000000..f1cc06b --- /dev/null +++ b/spec/swagger_rails/rspec/formatter_spec.rb @@ -0,0 +1,79 @@ +require 'swagger_rails/rspec/formatter' +require 'swagger_rails/rspec/dsl' +require 'rails_helper' + +RSpec.describe ::SwaggerRails::RSpec::Formatter do + include FormatterSupport + + def group + RSpec.describe('Ping API', swagger_doc: 'v1_api.json') do + path('/ping') do + post('checks if site is alive') do + consumes('application/json') + produces('application/json') + operation_description('A very long description') + response('200', '(OK) Site up and running') do + run_test! + end + end + end + end + end + + def send_notification_for_all_child_groups(group) + send_notification :example_group_finished, group_notification(group) + group.children.each do |child| + return if child.class.name == 'ValidRequest' + send_notification_for_all_child_groups(child) + end + end + + def swaggerize(*groups) + groups.each do |group| + group.run(reporter) + send_notification_for_all_child_groups group + end + end + + before(:each) do + swagger_root = (Rails.root + 'swagger').to_s + SwaggerRails::Configuration.new.tap { |c| c.swagger_root = swagger_root } + allow(RSpec.configuration).to receive(:swagger_docs).and_return({ 'v1_api.json' => { swagger: '2.0', + info: { title: 'API V1', + version: 'v1' } } }) + allow(RSpec.configuration).to receive(:swagger_root).and_return(swagger_root) + @formatter = SwaggerRails::RSpec::Formatter.new(StringIO.new) + end + + describe '#new' do + it 'should initialize the swagger_root' do + expect(@formatter.instance_variable_get(:@swagger_root)).to eq((Rails.root + 'swagger').to_s) + expect(@formatter.instance_variable_get(:@swagger_docs)).to eq({"v1_api.json"=>{:swagger=>"2.0", :info=>{:title=>"API V1", :version=>"v1"}}}) + end + end + + describe '#example_group_finished' do + before do + swaggerize(group) + end + + it "should print 'Generating Swagger Docs ...'" do + expect(formatter_output.string).to eq("Generating Swagger Docs ...\n") + end + + it 'should update the swagger_doc instance variable' do + expect(@formatter.instance_variable_get(:@swagger_docs) + ).to eq({ + 'v1_api.json' => { :swagger => '2.0', + :info => { :title => 'API V1', + :version => 'v1' }, + :paths => { '/ping' => { :post => { :tags => ['Ping API'], + :summary => 'checks if site is alive', + :description => 'A very long description', + :consumes => ['application/json'], + :produces => ['application/json'], + :parameters => [], + :responses => { '200' => { :description => '(OK) Site up and running' } } } } } } }) + end + end +end