With this ApplicationHelper:
class ApplicationHelper
def my_method
link_to 'foo', 'bar'
end
end
and this application_helper_spec:
require 'rails_helper'
describe ApplicationHelper do
describe 'links' do
it 'should call a helper method' do
expect(helper.my_method).to eq("<a href='bar'>foo</a>")
end
end
end
I'm having trouble getting things to work as as I expected from the latest documentation I can find on Rails helper specs. (The docs are for Ruby 3 and I'm using 4.) There doesn't appear to be a helper object:
undefined local variable or method `helper' for #<RSpec::ExampleGroups::ApplicationHelper::Links:0x007fda1895c2f8>
If instead I do this:
require 'rails_helper'
include ApplicationHelper
describe ApplicationHelper do
describe 'links' do
it 'should call a helper method' do
expect(my_method).to eq("<a href='bar'>foo</a>")
end
end
end
now my_method is called correctly but link_to is not defined:
undefined method `link_to' for #<RSpec::ExampleGroups::ApplicationHelper::Links:0x007fda1c4c3e90>
(This latter case is the same as if I define config.include ApplicationHelper in rails_helper.)
Obviously the spec environment does not include all the standard Rails helpers. What am I doing wrong here?
You need to either enable the infer_spec_type_from_file_location! option, or explicitly set the test type, e.g.:
describe ApplicationHelper, type: :helper do
...
end
I think that Andy Waite's answer regarding defining type: :helper on your spec is correct in that it will solve your undefined local variable or method 'helper' issues.
However, as for the overarching question of "How do I test helpers in Rails 4?", and specifically your method that seems to just make a call to ActionView::Helpers::UrlHelper#link_to, assuming that you don't want to cover this in a feature spec and want to test it in isolation, consider that if you want to test that "calling #my_method returns a string containing HTML for a foo bar link", the link_to tests for UrlHelper itself would confirm that calling link_to 'foo', 'bar' will return "<a href='bar'>foo</a>".
So, I'd suggest moving your specs up one level higher, saying that you want to test that "calling #my_method returns me a link for foo bar (in whatever way Rails hands me back links)":
require 'rails_helper'
RSpec.describe ApplicationHelper, type: :helper do
describe '#my_method' do
it 'returns a foo bar link' do
expect(helper).to receive(:link_to).with('foo', 'bar')
helper.my_method
end
end
end
Personally though, I don't think this method has enough logic in it to warrant testing in isolation in a helper spec, and you'd be better off covering it in a feature spec where I assume you'd be testing for the display of the link, or clicking it to see what happens etc.
Related
I'm making use of Services in my app, which is not one of the "standard" app components.
Let's say I have a spec test as follows
require "rails_helper"
# spec/services/create_user.rb
RSpec.describe CreateUser, type: :service do
it "returns the error message" do
error_message = Foo.new.errors
expect(error_message).to eq(t("foo.errors.message"))
end
end
Just testing that the string returned matches a specific translation string.
However this throws an error because the helper t() isn't available.
I could refer it to explicitly as I18n.t(), but for my own curiosity, how do I include the correct module to have the luxury of calling the shorthand form?
Thanks!
You should be able to add it to the RSpec configuration using;
RSpec.configure do |config|
config.include AbstractController::Translation
end
Which means you can then just use t() instead of I18n.t()
I have a Rails (4.2) helper that I am trying to unit test with Rspec 3.
# app/helpers/nav_helper.rb
module NavHelper
def nav_link(body, url, li_class: "", html: {})
li_class += current_page?(url) ? " active" : ""
content_tag(:li, class: li_class) do
link_to(body, url, html)
end
end
end
# spec/helpers/nav_helper_spec.rb
require 'spec_helper'
describe NavHelper do
describe "#nav_link" do
it "creates a correctly formatted link" do
link = nav_link("test", "www.example.com/testing")
...
end
end
end
This throws the following error when I run the test:
Failure/Error: link = nav_link("test", "www.example.com/testing")
NoMethodError:
undefined method `content_tag' for #<RSpec::ExampleGroups::NavHelper::NavLink:0x007fe44b98fee0>
# ./app/helpers/nav_helper.rb:5:in `nav_link'
It seems like the Rails helpers aren't available, but I'm not sure how to include them. Regardless, how can I test a helper method that uses content_tag?
Update
Adding include ActionView::Helpers::TagHelper throws the following error
uninitialized constant ActionView (NameError)
You need to include the helper that contains the content_tag method in your NavHelper (in this case, TagHelper):
module NavHelper
include ActionView::Helpers::TagHelper
# ...
end
It's a good idea to include only the helpers that you need to make things work, as it makes it clear which parts of Rails/ActionView you're using in your helper.
EDIT: Why is this necessary?
When you're testing the helper, you're testing it in isolation from the rest of Rails. That's why RSpec is complaining about the method not being available - it literally isn't there!
The issue was the leading line of my spec. I changed require 'spec_helper' to require 'rails_helper' and everything works as expected.
Not the first time that's bitten me, but it was the hardest.
I want to create a custom variable similar to response object that should only be available in controller specs. I noticed that rspec supports filters which are before/after hooks which means I can create instance variables with them to be used later. But response object feels and works more like a let variable that is lazily evaluated. Also, controller specs support assign method that can accept arguments.
Does rspec support any way to create similar methods to be used with a specific type of spec?
Note: I don't need to support anything below rspec 3.0.
You can simply do this by creating a module with your function and then including that in your RSpec configure block. You can control the types of specs where this should be available as a second parameter when you include the module:
module ControllerSpecHelpers
def something
'fubar2000'
end
end
RSpec.configure do |config|
config.include ControllerSpecHelpers, type: :controller
end
RSpec.describe BlahController, type: :controller do
it 'should be possible to use the `something` helper in a controller spec' do
expect(something).to eq('fubar2000')
end
end
How is it that rspec feature tests implicitly know to use methods such as find, within, and fill_in from the page object?
I've written a helper class for some of my rspec tests and wanted to use those methods, and realized that I needed to pass the page object into the method, and then use page.find and the like.
RSpec achieves this by including Capybara::DSL in those cases where it wants those methods available. The module is pretty elegant, if you want to take a look at https://github.com/jnicklas/capybara/blob/f83edc2a515a3a4fd80eef090734d14de76580d3/lib/capybara/dsl.rb
suppose you want to include the following module:
module MailerMacros
def last_email
ActionMailer::Base.deliveries.last
end
def reset_email
ActionMailer::Base.deliveries = []
end
end
to include them, just call config.include(MailerMacros), like this:
RSpec.configure do |config|
config.include(MailerMacros)
end
now, you should be able to call reset_email() & last_email instead of MailerMacros::reset_email().
I am writing an RSpec spec to test a Rails helper. The problem is that the helper method that I'm testing depends on a method defined in a different helper. It may be a code smell to me, but I'm adding tests for legacy code and am not at a point where I can refactor. How can I test this Rails helper?
module FancyHelper
def fancy_method
html = "hello"
html << another_fancy_method
html
end
end
module OtherHelper
def another_fancy_method
"world"
end
end
require "spec_helper"
describe FancyHelper do
describe "#fancy_method" do
it "does what it's supposed to" do
helper.fancy_method.should match("hello")
# NoMethodError: undefined method `another_fancy_method'
end
end
end
This is what stubs are for. When testing your helper that depends on the other helper, you will want to stub the other helper to get a predictable value and complete the test.
EDIT: https://www.relishapp.com/rspec/rspec-mocks/docs/method-stubs thanks grantovich for the newer link.