Skip to content

Warn on context fixture helper #1208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ language: ruby
rvm:
- 1.8.7
- 2.1.4
- 2.1.3
- 2.1.2
- 2.1.1
- 2.1.0
- 2.0.0
- 1.9.3
- 1.9.2
# - 2.1.3
# - 2.1.2
# - 2.1.1
# - 2.1.0
# - 2.0.0
# - 1.9.3
# - 1.9.2

env:
- RAILS_VERSION='~> 4.2.0.beta2'
- RAILS_VERSION=master
# - RAILS_VERSION=master
- RAILS_VERSION='~> 4.1.0'
- RAILS_VERSION=4-1-stable
- RAILS_VERSION='~> 4.0.4'
- RAILS_VERSION=4-0-stable
- RAILS_VERSION='~> 3.2.17'
- RAILS_VERSION=3-2-stable
- RAILS_VERSION='~> 3.1.12'
# - RAILS_VERSION=4-1-stable
# - RAILS_VERSION='~> 4.0.4'
# - RAILS_VERSION=4-0-stable
# - RAILS_VERSION='~> 3.2.17'
# - RAILS_VERSION=3-2-stable
# - RAILS_VERSION='~> 3.1.12'
- RAILS_VERSION='~> 3.0.20'

sudo: false
Expand Down
72 changes: 72 additions & 0 deletions lib/rspec/rails/fixture_context_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
module RSpec
module Rails
# @private
module FixtureContextHelpers
# @private
module TrackableFixtures
def known_fixtures
@_known_fixtures ||= []
end

def to_s
"#{super}(#{known_fixtures.sort.join(', ')})"
end
alias_method :inspect, :to_s
end

private

def convert_to_accessors(names)
names.map { |n| n.to_s.tr('/', '_').to_sym }
end

def ensure_prevent_fixture_helpers
if const_defined?(:PreventFixtures, false)
const_get(:PreventFixtures, false)
else
set_prevent_fixture_helpers
end
end

def prevent_fixture_helpers
const_get(:PreventFixtures) if const_defined?(:PreventFixtures)
end

def prevent_fixtures_in_context(accessor_names)
return if accessor_names.empty?
ensure_prevent_fixture_helpers.module_exec do
(accessor_names - known_fixtures).each do |accessor_name|
known_fixtures << accessor_name
define_method(accessor_name) do |*_fixture_names|
raise <<-EOS
Fixture accessor `#{accessor_name}` invoked in a `before` or `after` context hook at:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think before(:context) or after(:context) is clearer than before or after context.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll fix that up

#{CallerFilter.first_non_rspec_line}

Calling fixture accessors from a context hook is not supported.

Active Record fixtures are automatically loaded into the database before each
example. To ensure consistent data, the environment deletes the fixtures before
running the load each time.

Fixture accessor helpers are not intended to be called in a context hook, as
they exist to load database state that is reset between each example, while
`before(:context)` exists to define state that is shared across examples in an
example group and `after(:context)` exists to cleanup state that is shared
across examples in an example group.
EOS
end
end
end
end

def set_prevent_fixture_helpers
existing_helpers = prevent_fixture_helpers
mod = Module.new do
extend TrackableFixtures
include existing_helpers if existing_helpers
end
const_set(:PreventFixtures, mod)
end
end
end
end
29 changes: 29 additions & 0 deletions lib/rspec/rails/fixture_support.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require_relative 'fixture_context_helpers'
module RSpec
module Rails
# @private
Expand Down Expand Up @@ -26,6 +27,34 @@ module FixtureSupport
self.use_instantiated_fixtures = RSpec.configuration.use_instantiated_fixtures
fixtures RSpec.configuration.global_fixtures if RSpec.configuration.global_fixtures
end

# @private
module ClassMethods
include RSpec::Rails::FixtureContextHelpers

def run_before_context_hooks(example_group_instance)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you commented, it would be nice not to hook into a private rspec-core API like this. This may not continue working, for example, with the changes coming in rspec/rspec-core#1749.

I was also realizing that this issue bears similarity to the use of let or subject from :context hooks. We solved that in rspec-core in a one-off way, but since we've now seen that it's a more general issue, maybe we should build something into rspec-core that solves it in a general way and then refactor our let/subject stuff to use it, and you can also use it for this issue? Also, I was thinking there may be other rspec-rails (or rails) APIs that aren't valid to use from a :context hook...if so, again, it would benefit from a standardized solution that exposes a public API from rspec-core that rspec-rails could use.

Thinking out loud about a possible solution...there's nothing that says the example group instance used for :context hooks must be an instance of the example group class rather than an instance of some subclass that includes additional modules which override disallowed methods in order to issue warnings/errors when they are used. We could have a config API from rspec-core like config.include_in_context_hook_instances module, metadata or similar.

That said...I'm realizing that such a solution probably wouldn't play well with the changes in rspec/rspec-core#1749 since I'm working there on making shared contexts (including :context hooks) apply to the singleton class of the example group instance of examples that have matching metadata. For such a case, we're not going to be able to use an instance of a subclass.

Any ideas on what rspec-core could do to facilitate this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also realizing that this issue bears similarity to the use of let or subject from :context hooks.

Yes, I copied the wording and logic from there.

thinking there may be other rspec-rails (or rails) APIs that aren't valid to use from a :context hook

I believe there likely are. I know rspec-mocks would benefits from some helpful messages in :context hooks.

We could have a config API from rspec-core like config.include_in_context_hook_instances module, metadata or similar.

This would be ideal. That way any gem could hook into this process.

Any ideas on what rspec-core could do to facilitate this?

Not at this time. I'll need to review your PR a bit more to understand what is going on there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like the status of that other PR will affect what we do here...so I'm going to try to wrap up the rspec-core PR in the next day or two.

if (_helpers = prevent_fixture_helpers)
example_group_instance.extend _helpers
end
super
end
private :run_before_context_hooks

def run_after_context_hooks(example_group_instance)
if (_helpers = prevent_fixture_helpers)
example_group_instance.extend _helpers
end
super
end
private :run_before_context_hooks

def setup_fixture_accessors(table_names = nil)
fixture_set_names = Array(fixture_set_names || fixture_table_names)
prevent_fixtures_in_context(convert_to_accessors(fixture_set_names))
super
end
private :setup_fixture_accessors
end
end
end
end
Expand Down