I am looking for ways to reduce the default code for models, controllers and views.
I have just created a model SearchDescription with two fields and with a controller and view. I need the default scaffolded index,show etc. Also the rspecs, but again completely default. Nothing special. Just the default.
But as it turned out I am current committing some 20 files that are with a completely default behaviour. Nothing special.
Is there a way to do it cleaner with less code actually being generated in the project, but rather "dynamically generated"?
For example just write the following thing in a config file:
SearchDescription, field1, field2; with Controller; with views; with rspecs
and have this work by convention without actually generating the files?
Rails is all about readability, convention over configuration and about showing others code that is self-documentary. Although ... I've done testing in this way before, although we ended up using kind of the same functions and relayed them over the files that had to be there anyway to keep it clear for anyone to read.
The spec part might look something like this:
require 'spec_helper'
Initializing a 'config'-Hash that we can easy read and manipulate:
objectHash = { 'controller' => 'ClassController',
'object' => 'Class',
'engine' => 'Engine'
}
hash = { "#{objectHash['engine']}::#{objectHash['object'].capitalize}" => objectHash }
hash.each do |key, values|
describe Object.const_get("#{values['engine']}::#{values['controller']}"), :type => :controller do
login_user
before :each do
#object_string = "#{values['engine']}::#{values['object']}"
#object = FactoryGirl.create(Object.const_get(#object_string))
#example_attribute = :objectAttribute
end
This is the usual rails-way, just a little bit more abstract.
context "GET 'index'" do
it "should not be possible to access without permission" do
get :index, { use_route: values['engine'].to_sym }
expect(response).to have_http_status(302)
end
it "should be possible to access with permissions" do
permission_string = "#{values['engine'].downcase}_#{values['object'].underscore.pluralize.downcase}_index"
create_permissions(permission_string, false, true, subject.current_user)
get :index, { use_route: values['engine'].to_sym }
expect(response).to have_http_status(200)
end
end
end
and so on...
As you might imagine, this kind of code really breaks out of the "easy to understand" guidelines, we're all trying to follow.
And last you said you're writing tests for basic functionality? Does that really make sense to test the thing's you're aware of function anyway? I don't know exactly but it sounds like you're testing base-rails.
Related
I have just upgraded to Rails 5. In my specs I have the following
expect(model).to receive(:update).with(foo: 'bar')
But, since params no longer extends Hash but is now ActionController::Parameters the specs are failing because with() is expecting a hash but it is actually ActionController::Parameters
Is there a better way of doing the same thing in Rspec such as a different method with_hash?
I can get around the issue using
expect(model).to receive(:update).with(hash_including(foo: 'bar'))
But that is just checking if the params includes that hash, not checking for an exact match.
You could do:
params = ActionController::Parameters.new(foo: 'bar')
expect(model).to receive(:update).with(params)
However it still smells - you should be testing the behaviour of the application - not how it does its job.
expect {
patch model_path(model), params: { foo: 'bar' }
model.reload
}.to change(model, :foo).to('bar')
This is how I would test the integration of a controller:
require 'rails_helper'
RSpec.describe "Things", type: :request do
describe "PATCH /things/:id" do
let!(:thing) { create(:thing) }
let(:action) do
patch things_path(thing), params: { thing: attributes }
end
context "with invalid params" do
let(:attributes) { { name: '' } }
it "does not alter the thing" do
expect do
action
thing.reload
end.to_not change(thing, :name)
expect(response).to have_status :bad_entity
end
end
context "with valid params" do
let(:attributes) { { name: 'Foo' } }
it "updates the thing" do
expect do
action
thing.reload
end.to change(thing, :name).to('Foo')
expect(response).to be_successful
end
end
end
end
Is touching the database in a spec inheritenly bad?
No. When you are testing something like a controller the most accurate way to test it is by driving the full stack. If we in this case had stubbed out #thing.update we could have missed for example that the database driver threw an error because we where using the wrong SQL syntax.
If you are for example testing scopes on a model then a spec that stubs out the DB will give you little to no value.
Stubbing may give you a fast test suite that is extremely brittle due to tight coupling and that lets plenty of bugs slip through the cracks.
I handled this by creating in spec/rails_helper.rb
def strong_params(wimpy_params)
ActionController::Parameters.new(wimpy_params).permit!
end
and then in a specific test, you can say:
expect(model).to receive(:update).with(strong_params foo: 'bar')
It's not much different from what you're already doing, but it makes the awkward necessity of that extra call a little more semantically meaningful.
#max had good suggestions about how to avoid this altogether, and I agree they switched away from a hash to discourage using them with hashes interchangeably.
However, if you still want to use them, as a simple hack for more complex situations (for instance if you expect using a a_hash_including), you can try using something like this:
.with( an_object_satisfying { |o|
o.slice(some_params) == ActionController::Parameters.new(some_params)
})
I'm building a Rails application and formulating tests using RSpec.
I wrote tests for a method I'm creating called current_link_to. This method is supposed to check whether the current page corresponds to the path I pass it and add the current class to the generated link in case it does.
Here is the spec:
require "spec_helper"
describe ApplicationHelper do
describe "#current_link_to" do
let(:name) { "Products" }
let(:path) { products_path }
let(:rendered) { current_link_to(name, path) }
context "when the given path is the current path" do
before { visit(path) }
it "should return a link with the current class" do
# Uses the gem "rspec-html-matchers" (https://github.com/kucaahbe/rspec-html-matchers)
expect(rendered).to have_tag("a", with: { href: path, class: "current" }) do
with_text(name)
end
end
end
context "when the given path is not the current path" do
before { visit(about_path) }
it "should return a link without the current class" do
expect(rendered).to have_tag("a", with: { href: path }, without: { class: "current" } ) do
with_text(name)
end
end
end
end
end
I then tried implementing my method following the spec:
module ApplicationHelper
def current_link_to(name, path, options={})
options.merge!({ class: "#{options[:class]} current".strip }) if current_page?(path)
link_to(name, path, options)
end
end
However, the tests fail with the following error:
Failure/Error: let(:rendered) { current_link_to(name, path) }
RuntimeError: You cannot use helpers that need to determine the current page unless your view context provides a Request object in a #request method
Since I don't really need the current_page? helper method to perform checks on the request, I decided that it would make sense to stub it.
I tried the following methods, but none of them worked:
helper.double(:current_page? => true)
Seems to stub the helper.current_page? method, but it's not the same method that's being called by my function.
allow(ActionView::Helpers::UrlHelper).to receive(:current_page?).and_return(true)
The stub seems not to be effective at all
While writing this question I stumbled onto the solution. I managed to stub the current_page? method using this in a before block:
allow(self).to receive(:current_page?).and_return(true)
It worked, however this solution raised more questions than it really answered. I am now baffled over how this works, as it seems weird that self in a before block would respond to current_page? and that said method would in fact be exactly the same one my helper is calling.
Even after reading documentation and trying to figure out how this works by littering my code with puts calls, the following doubts still haunt me:
Why are helper methods available directly in the specs, when the RSpec docs mention that they should instead be available as methods on the helper object available in all helper specs?
How does stubbing the current_page? method on self in a RSpec before block somehow reflect onto the actual method that gets called by my helper? Does self in my helper for some reason reference the same self you can find in the before block? Is RSpec or Rails including and mixing stuff under the covers?
If the same self encompasses my spec and my helpers, what exactly does self refer to in this case and why is it the same everywhere?
It would be great if someone could help me figure this out because this is blowing my mind up, and I'm scared of using code that I don't really understand.
With respect, you're testing a little too much functionality here. The trick is to test only the bits you need to test.
In this instance, you only need to test that the current class is added when it needs to be, and isn't when it doesn't need to be.
This code should do the trick for you:
require 'rails_helper'
# Specs in this file have access to a helper object that includes
# the ApplicationHelper.
RSpec.describe ApplicationHelper, type: :helper do
describe 'current_link_to' do
let(:subject) { helper.current_link_to('some_name', 'some_path', options = {}) }
context 'where the path is current' do
before do
allow(helper).to receive(:current_page?).and_return true
end
it 'should include the current class' do
expect(subject).to match /current/
end
end
context 'where the path is not current' do
before do
allow(helper).to receive(:current_page?).and_return false
end
it 'should not include the current class' do
expect(subject).to_not match /current/
end
end
end
end
I've been a little glib and only tested for the presence of 'current' in the returned string. You could test for something like 'class="current"' if you want to be more precise.
The other key is the comment at the top of the page, which Rails inserts into blank helper specs for you:
# Specs in this file have access to a helper object that includes
# the ApplicationHelper.
That means that you can use 'helper' where in your comment above you were using 'self', which makes things a little clearer (imho)
Hope it helps!
I'm writing RSpec integration tests as I convert my spaghetti code to use accepts_nested_attributes_for. I have a snippet like this:
# file: spec/requests/wizard_spec.rb
describe 'POST /wizard with address' do
before(:each) do
#premise_attributes = {
"address"=>"600 Mellow Ave, Mellow Park, CA 94025, USA",
}
end
it 'should succeed' do
post :create, "wizard" => { "premise_attributes" => #premise_attributes }
response.status.should be(200)
end
end
Of course, this fails with:
Failure/Error: post :create, "wizard" => { "premise_attributes" => #premise_attributes }
ArgumentError:
bad argument(expected URI object or URI string)
Is there a method that converts the nested attributes hashes into a POST-able format?
(Related but less important: where is the post method documented or defined? I'd like to see what it really accepts as arguments.)
Instead post :create try use post "/wizard" or nest your specs inside describe WizardController do; end block. Generally you can use method :action syntax only if you're inside describe block for the given controller.
I found this while trying to fix my issue with trying to test put. My post method works though so maybe I can help you out if you still need it. I think your issue is that you're trying to update your attributes as if it was a scalar type variable, but nested attributes are really like an array. Rails generally names them "0", "1", etc., but I'm not sure it matters what the names are as long as they're unique. Give this a try:
#premise_attributes = {
"0" => {"address"=>"600 Mellow Ave, Mellow Park, CA 94025, USA"}
}
(By the way, the problem I'm having is that my update specs are failing because it says something like my address is not unique to borrow your example.)
I'm very rigorous when it comes to my HTML markup and I follow a strict coding convention for forms, lists, etc...
I would like to include reusable test in my RSpec tests that would allow for me call a form test from any other test and target it directly to the page or URL that I'm testing.
Something like this:
# spec/helpers/form_tester.rb
describe FormTester
it "should check to see if all the text fields have an ID prefix of 'input-'" do
... #form should be valid ...
should be true
end
end
# spec/requests/user_form.rb
describe UserForm
it "should validate the form" do
#form = find(:tag,'form')
# call the FormTester method
end
end
Any ideas on how todo this? I'm using Rails 3.1, with RSpec, Capybara and FactoryGirl.
Use shared examples. In you case, something like this may work:
# spec/shared_examples_for_form.rb
shared_examples 'a form' do
describe 'validation' do
it 'should be validated' do
form.should_be valid
end
end
end
# spec/requests/user_form.rb
describe UserForm
it_behaves_like 'a form' do
let(:form) { find(:tag, 'form') }
end
end
It's also possible to pass parameters to shared examples and place the shared examples inside spec/support. Just have a read at the documentation.
Shared examples are great, but you've got at least two serious problems here.
First: why are you giving your form fields IDs at all? You've already got perfectly good selectors: just use input[name='whatever']. And even if you are giving them IDs, don't put a prefix on them: input#whatever or just #whatever is probably a more sensible selector in your CSS than #input-whatever. By being overspecific on your selector names, you're most likely making your CSS and JavaScript harder to write than they have to be.
Second: don't use RSpec to test your views. RSpec is at its best when confined to models. Cucumber is better for anything user-facing.
I feel like this is a not-so-much documented topic, at least I've had a lot of trouble finding our about the best practices here.
I'm fragment caching in the view using a cache_key:
%tbody
- #employees.each do |employee|
- cache employee do
%tr[employee]
%td= employee.name
%td= employee.current_positions
%td= employee.home_base
%td= employee.job_classes
Now I can add :touch => true on the :belongs_to side of my has_many associations and this will do everything I need to keep this fragment caching up to date, but for the life of me I'm having a hard time figuring out how to test this.
Dropping in :touch => true is easy and convenient but it spreads the expiry logic around a couple places. I'd love to have an RSpec request spec that walks through and checks the behavior on this, something that isn't liable to change much but can bring all the caching requirements into one specific file that describes what is supposed to be occurring.
I tried along these lines:
require 'spec_helper'
include AuthenticationMacros
describe "Employee index caching" do
before do
Rails.cache.clear
ActionController::Base.perform_caching = true
login_confirmed_employee
end
after do
ActionController::Base.perform_caching = false
end
specify "the employee cache is cleared when position assignments are modified"
specify "the employee cache is cleared when home base assignments are modified"
end
The specs were fleshed out with the Capybara steps of going through and making the updates of course, and I thought I was quite on the right track. But the tests were flickering in weird ways. I would modify the specs to output the employee objects cache_key, and sometimes the cache_keys would change and sometimes not, sometimes the specs would pass and sometimes not.
Is this even a good approach?
I know SO wants questions that are answerable, so to start: how can I set up and tear down this test to use caching, when my test env does not have caching on by default? In general, however, I'd really like to hear how you might be successfully testing fragment caching in your apps if you have had success with this.
EDIT
I'm accepting cailinanne's answer as it addresses the problem that I specifically ask about, but I have decided however that I don't even recommend integration testing caching if you can get away from it.
Instead of specifying touch in my association declarations, I've created an observer specific to my caching needs that touches models directly, and am testing it in isolation.
I'd recommend if testing a mulit-model observer in isolation to also include a test to check the observers observed_models, otherwise you can stub out too much of reality.
The particular answer that lead me to this is here: https://stackoverflow.com/a/33869/717365
Let me first say that in this answer, you may get more sympathy then fact. I've been struggling with these same issues. While I was able to get reproducible results for a particular test, I found that the results varied according to whether or not I ran one versus multiple specs, and within or without spork. Sigh.
In the end, I found that 99.9% of my issues disappeared if I simply enabled caching in my test.rb file. That might sound odd, but after some thought it was "correct" for my application. The great majority of my tests are not at the view/request layer, and for the few that are, doesn't it make sense to test under the same configurations that the user views?
While I was wrestling with this, I wrote a blog post that contains some useful test helpers for testing caching. You might find it useful.
Here is what I've used in my specs with caching enabled in my config/environments/test.rb
require 'spec_helper'
include ActionController::Caching::Fragments
describe 'something/index.html.erb' do
before(:each) do
Rails.cache.clear
render
end
it 'should cache my fragment example' do
cached_fragment = Rails.cache.read(fragment_cache_key(['x', 'y', 'z']))
cached_fragment.should have_selector("h1")
end
end
I use view specs to test cache expiration in views:
describe Expire::Employee, type: :view, caching: true do
def hour_ago
Timecop.travel(1.hour.ago) { yield }
end
def text_of(css, _in:)
Nokogiri::HTML(_in).css(css).text
end
let(:employee) { hour_ago { create :employee } }
def render_employees
assign(:employees, [employee.reload])
render(template: 'employees/index.html.erb')
end
alias_method :employees_list, :render_employees
context "when an employee's position gets changed" do
let(:position) { create :position, employee: employee }
before { hour_ago { position.update!(name: 'Old name') } }
let(:update_position) { position.update!(name: 'New name') }
it "should expire the employee's cache" do
expect { update_position }
.to change { text_of('.positions', _in: employees_list) }
.from(/Old name/).to(/New name/)
end
end
# similar spec case for home base assignment
end
where
Timecop gem is used to travel in time to make sure that cache key timestamps are different for different cache versions of employee
Nokogiri gem is used to extract employee position's text from the rendered view
Note that I tagged this spec with caching: true. It enables caching before each test case and disables after it:
config.before(:each, caching: true) do
controller.perform_caching = true
end
config.after(:each, caching: true) do
controller.perform_caching = false
end
And you might want to add an example that checks that an employee is being actually cached
describe Expire::Employee, type: :view, caching: true do
context 'with an uncached employee' do
it 'should cache the employee' do
expect_any_instance_of(Employee)
.to receive(:current_positions).once
2.times { render_employees }
end
end
# other spec cases
end