Skip to content
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

Feature: Sanctioned Support Helper Inclusion #1943

Closed
fny opened this issue Apr 22, 2015 · 4 comments
Closed

Feature: Sanctioned Support Helper Inclusion #1943

fny opened this issue Apr 22, 2015 · 4 comments

Comments

@fny
Copy link

fny commented Apr 22, 2015

I got this brilliant error message today:

`method_missing': `whatever` is not available on an example group (e.g. a `describe` or `context` block). It is only available from within individual examples (e.g. `it` blocks) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). (RSpec::Core::ExampleGroup::WrongScopeError)

Then I began to wonder why there's no built in way to include support helpers easily. Here's a rough sketch of what I'm thinking would be incredibly useful:

RSpec.support(:controller_support, type: :controller) do
  group_helpers do
    def helper_for_a_describe_block
    end
  end
  example_helpers do
    def helper_for_a_let_block
    end
    def helper_for_an_it_block
    end
  end
end

I was earlier repeating this pattern all over the place:

module ControllerSupport
  # Methods to add to both example groups and examples
  module Global
  end

  # Methods to add to example groups (i.e. `describe` or `context` blocks)
  module GroupHelpers
  end

  # Add methods to add to examples (i.e. `it` blocks) or constructs that run in
  # the scope of an example (e.g. `before`, `let`, etc)
  module ExampleHelpers; end
end

RSpec.configure do |config|
  config.extend ControllerSupport::Global, type: :controller
  config.extend ControllerSupport::GroupHelpers, type: :controller
  config.include ControllerSupport::Global, type: :controller
  config.include ControllerSupport::ExampleHelpers, type: :controller
end

Right now, I use this hackiness to do the dirty work, and it's been a pleasure so far:

def RSpec.support(module_name, options = {})
  RSpec.configure do |config|
    if defined?(module_name::Global)
      config.extend module_name::Global, options
      config.include module_name::Global, options
    end
    if defined?(module_name::GroupHelpers)
      config.extend module_name::GroupHelpers, options
    end
    if defined?(module_name::ExampleHelpers)
      config.include module_name::ExampleHelpers, options
    end
  end
end

# Now I can do this!
module ModelSupport; end
RSpec.support(ModelSupport, type: :model)

I'd love to implement this myself with some guidance if you all are interested.

Cheers! 🍻

@myronmarston
Copy link
Member

I don't understand what your proposed feature would provide for us over config.include or config.extend. What is the advantage?

@fny
Copy link
Author

fny commented Apr 22, 2015

A few things come to mind immediately:

  • Overall, I find this a bit more "user friendly": the points below will expand on that
  • It's not obvious what extend and include do, something more DSL-like could clarify that
  • Defining modules for support files pollutes the global namespace; I've had clashes occur when working in teams (plus I don't think I've ever called a module from outside of a single support file); ideally RSpec could define these modules and tuck them away somewhere we're they'd still be accessible
  • Global support helpers are a bit cumbersome to write since you need to write a line for both include and extend
  • There's a lot of repetition when the above pattern (see module ControllerSupport) is repeated a few times across different spec metadata (e.g. models, controllers, views, features); this gets worse if you divide up support files for a spec type across several modules (e.g. auth_support, current_user_support, job_support)
  • There's unnecessary connascence between the module name and the include/extend call: any modification to the module name would require two or more changes

@myronmarston
Copy link
Member

Does shared_context mean your needs? Your original example can easily be done with one:

RSpec.shared_context "controller support", type: :controller do
  def self.helper_for_a_describe_block
  end

  def helper_for_a_let_block
  end

  def helper_for_an_it_block
  end
end

(Note, however, that we're discussing changes to the semantics of shared group metadata in #1790).

Anyhow you argue well :). Responses to some of your specific points below:

It's not obvious what extend and include do

You're the first user I've ever heard say that. What do you find non-obvious about it? Can we improve our docs somehow?

I've always thought (and from what I've heard from users, the community seems to agree) that it's very obvious given that it just delegates to Module#include and Object#extend, which are well-known Ruby APIs. Using an alternate API when we can use the existing one that's named after ruby concepts makes more sense to me.

something more DSL-like could clarify that

IMO, the DSL here would obscure, not clarify. It feels like "DSL for the sake of DSL". RSpec definitely supports a rich DSL but we generally don't want to add additional DSL when existing APIs provide the same functionality.

Defining modules for support files pollutes the global namespace; I've had clashes occur when working in teams (plus I don't think I've ever called a module from outside of a single support file); ideally RSpec could define these modules and tuck them away somewhere we're they'd still be accessible

You can also just use a local variable instead of a global constant:

module = Module.new do
  # define stuff here...
end

RSpec.configure do |config|
  config.include module
end

Global support helpers are a bit cumbersome to write since you need to write a line for both include and extend

You can use a shared context and write methods as def foo or def self.foo to make them available at the instance (example) level or class (group) level.

There's unnecessary connascence between the module name and the include/extend call: any modification to the module name would require two or more changes

Then do this:

module MyHelpers
  # method defs here

  RSpec.configuration.include self
end

Anyhow, given that we already have module inclusion, extension and shared contexts, adding yet another way to define and include helper methods seems like a confusing change for users. If you created this as an extension gem and it got some traction in the community, we'll be glad to reconsider including it in rspec-core.

@fny
Copy link
Author

fny commented Apr 22, 2015

Just want to say I really appreciate you taking the time to write out your
thoughts. I'll probably write up the gem just for kicks. Thanks again!

On Wed, Apr 22, 2015 at 1:34 AM, Myron Marston notifications@github.com
wrote:

Closed #1943 #1943.


Reply to this email directly or view it on GitHub
#1943 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants