I have a function that sends a variable to js with the help of gon.
def calc_foo
# calculate foo
gon.foo = foo
end
I want to test this function i.e make sure that the method return the correct value, using rspec.
it "should return bar" do
foo = #foo_controller.calc_foo
expect(foo).to eq(bar)
end
But, I get the following error message when the test case reaches the line where the variable is sent to gon.
Failure/Error: foo = #foo_controller.calc_foo
NoMethodError:
undefined method `uuid' for nil:NilClass
I have checked the value for foo, and it is not Nil, so gon must be Nil.
I believe the error is that I don't incude gon correctly. This is the rspec-part of my Gemfile
#rspec-rails includes RSpec itself in a wrapper to make it play nicely with Rails.
#Factory_girl replaces Rails’ default fixtures for feeding test data
#to the test suite with much more preferable factories.
group :development, :test do
gem 'rspec-rails'
gem 'factory_girl_rails'
gem 'capybara'
gem 'gon'
end
So how can I get rspec to play nicely with gon?
(I have also tried to include gon in my spec-file with no success)
In the controller specs (where gon belongs) you'll need to make an actual request to bypass your problem:
RSpec.describe ThingiesController do
let(:gon) { RequestStore.store[:gon].gon }
describe 'GET #new' do
it 'gonifies as expected' do
get :new, {}, valid_session # <= this one
expect(gon['key']).to eq :value
end
end
end
If you'd rather not stick with certain controller or action for the gon-related specs (let's say you have a gon-related method in your ApplicationController), you could use Anonymous controller approach:
RSpec.describe ApplicationController do
let(:gon) { RequestStore.store[:gon].gon }
controller do
# # Feel free to specify any of the required callbacks,
# # like
# skip_authorization_check
# # (if you use `CanCan`)
# # or
# before_action :required_callback_name
def index
render text: :whatever
end
end
describe '#gon_related_method' do
it 'gonifies as expected' do
get :index
expect(gon['key']).to eq :value
end
end
end
I have lots of controller and request/integration specs and can confirm that gon behaves fine there as long as you make actual requests.
However, I still have a problem similar to yours although the case is different (making requests from the shared_examples included in the controller specs). I've opened the relevant issue, feel free to join the conversation (for anyone interested).
I test that the controller passes the right stuff to gon in a request spec.
The controller sets an array of objects -- e.g., gon.map_markers = [...]
My request spec extracts the JSON via regexp (the .split() and match_array handle the order-independent-array-ness):
....
# match something like
# gon.map_markers=[{"lat":a,"lng":b},{"lat":c,"lng":d},...];
# and reduce/convert it to
# ['"lat":a,"lng":b',
# '"lat":c,"lng":d',
# ...
# ]
actual_map_markers = response.body
.match('gon.map_markers=\[\{([^\]]*)\}\]')[1]
.split('},{')
expect(actual_map_markers).to match_array expected_map_markers
Related
I am new with Ruby/Rails and the testing frameworks within Ruby/Rails. I have a pre-validation method (external API) that validates the incoming request. For all test cases, I want to stub that call and test the remaining functionalities.
I am knowledgeable about testing and mocks/stubs/spies (mostly Mockito/Powermockito stuffs), but do not know my way around Rails testing. I tried looking into RSpec / MiniTest stuffs, but it is getting overwhelming.
I have a controller method like this:
def handler
# validate
request_validated = validate_request
unless request_validated
head :unauthorized
return
end
#... remaining codes
end
def validate_request
# validation with external API
end
I have controller tests set up using ActionController::TestCase. Prior to adding the validation stuffs, all my test cases have tested out. But I cannot stub around the validation check.
I would want to have something like
controller.stub(validate_request).then_and_return(true) # need something of this sort
post :handler, as: :json, params: completed_service_parameters
assert_response :no_content
I'm open to using any library, though would prefer to use any Rails in-built, if there's anything. Thanks.
I ended up using 'minitest/stub_any_instance'
require 'minitest/stub_any_instance'
...
test 'test stub' do
...
Controller.stub_any_instance(:function, return-value) do
# perform the call within the stubbed block
post :function, as: :json, params: { :param1 => 'random' }
end
...
end
Shouldn't I be able to see instance variables which are created in a controller action from within my rspect tests?
# /app/controllers/widget_controller.rb
...
def show
#widget = ...
puts "in controller: #{#widget}"
end
...
--
# /spec/controllers/widget_controller_spec.rb
RSpec.describe WidgetController, type: :controller do
...
describe "GET #show" do
it "assigns the requested widget as #widget" do
get :show, { :id => 1 } # this is just an example - I'm not hardcoding the id
puts "in spec: #{#widget}"
end
end
...
Here is the output I get when I run that spec:
controller: #<Widget:0x007f9d02aff090>
in spec:
Am I wrong in thinking that I should have access to #widget in my controller spec?
Use the assigns method (*note: assigns is now deprecated. See bottom of my answer for info):
it "assigns the #widget"
expect(assigns(:widget)).not_to be_nil
end
Of course, you can inspect widget as you'd like, but without seeing what #widget is from your provided controller code, I just checked if it was nil
If you're wanting to puts the widget, like in your example, just print it with assigns
puts assigns(:widget)
EDIT: assigns is now deprecated (see: https://apidock.com/rails/ActionController/TestProcess/assigns)
If you want to continue using assigns you will need to install the rails-controller-testing gem.
Otherwise, you could use what rails-controller-testing gem uses internally: #controller.view_assigns[]
#assigns is deprecated. Here's a solution that avoids pulling in the rails-controller-testing gem
Though it can be a code smell to test instance variables in controllers, to upgrade older apps you can leverage #instance_variable_get.
Rspec example:
expect(#controller.instance_variable_get(:#person).id).to eq(person_id)
In Rails 5, assigns has now been split out into a new rails-controller-testing gem.
You can either install the gem to continue using assigns, or you could use what the rails-controller-testing gem uses internally, which#controller.view_assigns[].
In new Rails versions with rails-controller-testing gem you can access to instance variables two ways:
with assigns[:key] / assigns['key']
with controller.view_assigns['key']
As you see assigns is ActiveSupport::HashWithIndifferentAccess, so you you can use both a string or symbol as key. It is formed with regular_writer method from the Hash, where all the keys with instance variables are strings, i.e. from #controller.view_assigns. But you can also access to controller inside tests
Here example
require 'rails_helper'
describe SomeController, type: :controller do
before do
assign(:some_var, 10)
get :show, params: { id: 1 }
end
it 'assigns the #some_var'
expect(assigns['some_var']).to eq 10
expect(assigns[:some_var]).to eq 10
expect(controller.view_assigns['some_var']).to eq 10
end
end
I have a class for constructing some user parameters by getting a section of params from a post request and adding some user agent information to them. I'm using the browser gem for this purpose.
The gem adds a helper method called browser, that inspects your current user agent. Its use is as simple as this:
require "browser"
browser.name # readable browser name
browser.version # major version number
A short section of my class looks like so:
class AkUserParams
def self.create(params)
#user_params = params[:signature]
#user_params[:user_agent] = browser.user_agent
#user_params
end
end
Using the browser gem throughout my application works fine. However, when I write a spec in RSpec for the class, I get the following error:
NameError: undefined local variable or method 'browser' for AkUserParams:Class
describe AkUserParams do
# I have tried both with and without this before block:
before :all do
$browser = Browser.new
end
let(:some_params) {{
signature: {
name: Faker::Name.name,
email: Faker::Internet.email,
}
}}
# This is a bogus test I'm expecting to fail. It doesn't get to
# failing and instead gives me a name error.
it 'Builds an object containing data about the user and the action' do
expect(AkUserParams.create(petition_signature)).to eq(true)
end
end
Update: I fixed the issue by just injecting browser:
class AkUserParams
def self.create(browser, params)
#user_params = params[:signature]
#user_params[:user_agent] = browser.user_agent
#user_params
end
end
That way I can just let(:browser) { Browser.new } in the spec.
I have a bunch of codes with repeating structures in a feature test in Rails. I would like to dry up my spec by reusing the structure. Any suggestions?
An example is:
feature "Search page"
subject { page }
it "should display results"
# do something
within "#A" do
should have_content("James")
end
within "#B" do
should have_content("October 2014")
end
# do something else
# code with similar structure
within "#A" do
should have_content("Amy")
end
within "#B" do
should have_content("May 2011")
end
end
At first, I tried to define a custom matcher in RSpec, but when I add within block, it did not seem to work. My guess is within is specific to Capybara, and cannot be used in custom matcher in RSpec.
Why not factor the common code into helper methods in a module. Then you can include that module in your spec_helper.rb file
I usually put common code like user_login in such a module in a file in the spec/support folder
spec_helper.rb
#Load all files in spec/support
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
#some config
config.include LoginHelper
#more config
end
spec/support/login_helper.rb
module LoginHelper
def do_login(username, password)
visit root_path
within("#LoginForm") do
fill_in('username', :with => username)
fill_in('password', :with => password)
click_button('submit')
end
end
end
I don't think you're using within as a matcher, since a matcher would be used after a should, should_not, etc. You can load custom, non-matcher methods into your specs by writing a module and including it in your spec_helper.rb config block, e.g.:
spec/support/my_macros.rb
module MyMacros
def within(tag, &block)
# your code here
end
end
spec/spec_helper.rb
require 'spec/support/my_macros'
...
RSpec.configure do |config|
config.include MyMacros
...
end
I'm using Capybara + Cucumber for end-to-end testing. In the end, I think I've pretty much done what both #hraynaud and #eirikir suggest (directionally speaking) - although the details are different since I'm in the Cucumber context. So, consider this not a whole different idea - but maybe a slightly more complete description and discussion. Also, note that my examples focus on testing results - not navigation and form filling. Since it looked like you were in a testing mindset (given your use of should have_content), I thought this might be of interest.
In general, my approach is:
Wrap Capybara tests in validation helper methods within a module. The motivation for wrapping is (a) to save me from having to remember Capybara syntax and (b) to avoid having to type all those repetitive test statements. Also, it ends up making my tests cleaner and more readable (at least for me).
Create a generic validate method that receives (i) a validation helper method name (as a symbol) and (ii) an array of items each of which is to be passed to the validation helper. The validate method simply iterates over the array of items and calls the validation helper method (using the send method), passing each item along with each call.
Attach the helpers and generic validate method to World (read more about World here) so that they are available throughout my Cucumber tests.
Enjoy testing happiness!
Steps 1-3 happen in a file called form_validation_helpers.rb.
features/support/form_validation_helpers.rb
module FormValidationHelpers
...more methods before
# ============================================================================
# Tests that an element is on the page
# ============================================================================
def is_present(element)
expect(find(element)).to be_truthy
end
# ============================================================================
# Tests for the number of times an element appears on a page
# ============================================================================
def number_of(options={})
page.should have_css(options[:element], count: options[:count])
end
# ============================================================================
# Checks that a page has content
# ============================================================================
def page_has_content(content)
page.should have_content(content)
end
...more methods after
# ============================================================================
# The generic validation method
# ============================================================================
def validate(validation, *items)
items.each do |item|
send(validation, item)
end
end
end
World(FormValidationHelpers)
Step 4 (from above) happens in my step files.
features/step_definitions/sample_steps.rb
Then(/^she sees the organization events content$/) do
validate :number_of,
{element: 'ul#organization-tabs li.active', is: 1}
validate :is_present,
"ul#organization-tabs li#events-tab.active"
validate :page_has_content,
"A Sample Organization that Does Something Good",
"We do all sorts of amazing things that you're sure to love."
end
As you can see from the validate :page_has_content example, I can run the test multiple times by adding the appropriate arguments onto the validate call (since the validate method receives everything after the first argument into an array).
I like having very specific selectors in my tests - so I can be sure I'm testing the right element. But, when I start changing my view files, I start breaking my tests (bad) and I have to go back and fix all the selectors in my tests - wherever they may be. So, I made a bunch of selector helpers and attached them to World the same as above.
features/support/form_selectors_helpers.rb
module FormSelectorsHelper
...more _selector methods before
def event_summary_selector
return 'input#event_summary[type="text"]'
end
...more _selector methods after
end
World(FormSelectorsHelper)
So now, I have only one place where I need to keep my selectors up to date and accurate. Usage is as follows (note that I can pass whatever the validation helper method needs - strings, methods, hashes, arrays, etc.)...
features/step_definitions/more_sample_steps.rb
Then(/^she sees new event form$/) do
validate :is_present,
event_summary_selector,
start_date_input_selector,
start_time_input_selector,
end_time_input_selector
validate :is_absent,
end_date_input_selector
validate :is_unchecked,
all_day_event_checkbox_selector,
use_recur_rule_checkbox_selector
validate :is_disabled,
submit_button_selector
validate :has_text,
{ element: modal_title_bar_selector, text: "Okay, let's create a new event!" }
end
Turning back to your question, I imagine you could end up with something like:
feature "Search page"
subject { page }
it "should display results"
# do something
validate :has_content_within,
[a_selector, "James"],
[b_selector, "October 2014"]
# do something else
validate :has_content_within,
[a_selector, "Amy"],
[b_selector, "May 2011"]
end
Capybara Test Helpers provides a nice way to encapsulate test code when using Capybara + RSpec.
RSpec.feature "Search page", test_helpers: [:search] do
before do
visit search_path
end
it "should display results"
search.filter_by(name: 'James')
search.should.have_result(name: 'James', date: 'October 2014')
search.filter_by(name: 'Amy')
search.should.have_result(name: 'Amy', date: 'May 2011')
end
end
You can then implement your own actions and assertions as needed:
class SearchTestHelper < Capybara::TestHelper
aliases(
name_container: '#A',
date_container: '#B',
)
def filter_by(attrs)
attrs.each { |key, name| ... }
click_link('Search')
end
def have_result(name:, date:)
have(:name_container, text: name)
within(:date_container) { have_content(date) } # equivalent
end
end
You can read the guide here.
I'm looking for a clean way to use JBuilder and test the json output with RSpec. The popular way for JSON testing is to implement the as_json method, and then in RSpec compare the received object with the object.to_json method. But a large reason I'm using JBuilder is that I don't want all the attributes that to_json spits out; so this breaks comparison.
Currently with JBuilder I'm having to do the following to test the RSpec results:
1) Create a Factory object: #venue
2) Create a hash inside my RSpec test that contains the "expected" JSON string back
#expected => {:id => #venue.id,:name=>#venue.name..........}
2) Compare the #expected string to the results.response.body that is returned from the JSON call.
This seems simple, except I have objects being rendered with 15+ attributes, and building the #expected hash string is cumbersome and very brittle. Is there a better way to do this?
You should be able to test your Jbuilder views with RSpec views specs. You can see the docs at https://www.relishapp.com/rspec/rspec-rails/v/2-13/docs/view-specs/view-spec.
An example spec for a file located at 'app/views/api/users/_user.json.jbuilder', could be something like this (spec/views/api/users/_user.json.jbuilder_spec.rb):
require 'spec_helper'
describe 'user rendering' do
let(:current_user) { User.new(id: 1, email: 'foo#bar.com') }
before do
view.stub(:current_user).and_return(current_user)
end
it 'does something' do
render 'api/users/user', user: current_user
expect(rendered).to match('foo#bar.com')
end
end
I don't like testing the JSON API through the views, because you have to essentially mimic, in the test, the setup already done in the controller. Also, bypassing the controller, you aren't really testing the API.
In controller tests, however, you'll find that you don't get any JSON returned in the response body. The response body is empty. This is because RSpec disables view rendering in controller tests. (For better or worse.)
In order to have an RSpec controller test of your view rendered JSON API, you must add the render_views directive at the top of your test. See this blog post (not mine), for more detailed information about using RSpec controller tests with Jbuilder.
Also, see this answer.
I have not been able to make RSpec work with the views yet, but I am testing my JSON API via controller RSpec tests. To assist with this process, I am using the api matchers gem. This gem lets you construct RSpec tests such as:
it "should have a 200 code" do
get :list, :format => :json
response.should be_success
response.body.should have_json_node( :code ).with( "200" )
response.body.should have_json_node( :msg ).with( "parameter missing" )
end
This sounds like a good use case for RSpec view specs. Are you using JBuilder for the output of a controller in views?
For example, in spec/views/venues_spec.rb
require 'spec_helper'
describe "venues/show" do
it "renders venue json" do
venue = FactoryGirl.create(:venue)
assign(:venue, venue])
render
expect(view).to render_template(:partial => "_venue")
venue_hash = JSON.parse(rendered)
venue_hash['id'].should == #venue.id
end
end
It's a little clunkier than with say ERB, but you can use binding and eval to run the Jbuilder template directly. E.g. given a typical Jbuilder template app/views/items/_item.json.jbuilder that refers to an instance item of the Item model:
json.extract! item, :id, :name, :active, :created_at, :updated_at
json.url item_url(item, format: :json)
Say you have an endpoint that returns a single Item object. In your request spec, you hit that endpoint:
get item_url(id: 1), as: :json
expect(response).to be_successful # just to be sure
To get the expected value, you can evaluate the template as follows:
item = Item.find(1) # local variable `item` needed by template
json = JbuilderTemplate.new(JbuilderHandler) # local variable `json`, ditto
template_path = 'app/views/items/_item.json.jbuilder'
binding.eval(File.read(template_path)) # run the template
# or, for better error messages:
# binding.eval(File.read(template_path), File.basename(template_path))
expected_json = json.target! # template result, as a string
Then you can compare the template output to your raw HTTP response:
expect(response.body).to eq(expected_json) # plain string comparison
Or, of course, you can parse and compare the parsed results:
actual_value = JSON.parse(response.body)
expected_value = JSON.parse(expected_json)
expect(actual_value).to eq(expected_value)
If you're going to be doing this a lot -- or if, for instance, you want to be able to compare the template result against individual elements of a returned JSON array, you might want to extract a method:
def template_result(template_path, bind)
json = JbuilderTemplate.new(JbuilderHandler)
# `bind` is passed in and doesn't include locals declared here,
# so we need to add `json` explicitly
bind.local_variable_set(:json, json)
bind.eval(File.read(template_path), File.basename(template_path))
JSON.parse(json.target!)
end
You can then do things like:
it 'sorts by name by default' do
get items_url, as: :json
expect(response).to be_successful
parsed_response = JSON.parse(response.body)
expect(parsed_response.size).to eq(Item.count)
expected_items = Item.order(:name)
expected_items.each_with_index do |item, i| # item is used by `binding`
expected_json = template_result('app/views/items/_item.json.jbuilder', binding)
expect(parsed_response[i]).to eq(expected_json)
end
end
You can call the render function directly.
This was key for me to get local variables to work.
require "rails_helper"
RSpec.describe "api/r2/meditations/_meditation", type: :view do
it "renders json" do
meditation = create(:meditation)
render partial: "api/r2/meditations/meditation", locals: {meditation: meditation}
meditation_hash = JSON.parse(rendered)
expect(meditation_hash['slug']).to eq meditation.slug
expect(meditation_hash['description']).to eq meditation.description
end
end